OverlappingInstances有什么好的用例吗? [英] Are there any good use cases for OverlappingInstances?

查看:106
本文介绍了OverlappingInstances有什么好的用例吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在设计一个可以使用OverlappingInstances编译器标志大大受益的库。但每个人都会谈关于这个扩展名,并警告它的危险。我的问题是,有没有在扩展的任何地方很好地使用这种扩展的例子?有没有什么经验法则来说明如何封装不良并正确使用扩展?

解决方案

也许一个想法实验会让这个扩展有点神秘。



让我们假装我们已经放弃了使用多个模式实例定义的函数必须全部在一个地方的限制,以便您可以编写 foo(bar,Nothing)= ... 在一个模块的顶部,然后在其他地方有类似 foo(baz,Just x)= ... 的情况。实际上,让我们走得更远,并允许在不同模块中完全定义案例



如果您认为听起来像是混淆和错误倾向于使用,你是正确的。



为了恢复一些表面的理智,我们可以添加一些限制。例如(ha,ha),我们可以要求以下属性:


  • 使用这样一个函数,给定的参数必须匹配恰好一个模式。其他任何事情都是编译器错误。
  • 添加新的模式(包括通过导入另一个模块)不应该改变有效代码的含义 - 要么选择相同的模式,要么编译器错误是。


    应该很清楚,匹配 True Nothing 很简单。我们还可以手工编写一些东西,并假定编译器可以消除文字歧义,如barbaz以上。

    另一方面,像(x,Just y)这样的模式绑定参数变得很笨拙 - 编写这样的模式意味着放弃编写像(True,_)(False,只是foobar)以后,因为这会造成不明确性。更糟糕的是,花纹防护几乎变得毫无用处,因为它们需要非常普遍的匹配。许多常见的习语会产生无穷无尽的含糊不清的头痛,当然写一个默认穿透模式是完全不可能的。

    这大致就是类型实例的情况。

    我们可以通过放宽所需的属性来恢复一些表现力:


    • 使用这样一个函数,它必须至少匹配至少一个模式。没有匹配是编译器错误。
    • 如果使用一个函数使多个模式匹配,那么将使用最具体的模式。如果没有唯一的最特定模式,则会产生错误。

    • 如果函数以与一般实例相匹配的方式使用,但可以在运行时应用于参数会匹配更具体的实例,这是一个编译器错误。



    请注意,我们现在处于仅导入模块的情况通过引入一个新的,更具体的模式来改变函数的行为。在涉及高阶函数的复杂情况下,事物可能会变得模糊不清。尽管如此,在很多情况下,问题不太可能 - 例如,在库中定义一个通用的贯穿模式,同时让客户代码添加特定的案例(如果需要的话)。



    其中 OverlappingInstances 会为您提供帮助。正如上面的例子所示,如果创建新的重叠总是不可能或者想要的,而且不同的模块最终不会看到不同的,冲突的实例,那么它可能是好的。



    真正归结到的是, OverlappingInstances 被删除的限制可以让使用类型的类在开放世界的假设下行为合理,任何可能的实例都可能会被添加。通过放宽这些要求,你自己承担了这种负担;所以想想通过所有可以添加新实例的方式,以及这些方案中是否存在重大问题。如果您确信即使在晦涩晦涩的角落案件中也没有任何事情会发生,那么请继续使用该扩展程序。


    I'm designing a library that would greatly benefit from the use of the OverlappingInstances compiler flag. But everyone talks smack about this extension and warns of its dangers. My question is, are there examples of a good use of this extension anywhere on hackage? Is there any rule of thumb on how to encapsulate the badness and use the extension properly?

    解决方案

    Perhaps a thought experiment will de-mystify this extension a bit.

    Let's pretend that we've dropped the restriction that functions defined with multiple pattern cases must be all in one place, so that you can write foo ("bar", Nothing) = ... at the top of a module, and then have cases like foo ("baz", Just x) = ... elsewhere. In fact, let's go even further, and allow cases to be defined in different modules entirely!

    If you think that sounds like it would be confusing and error-prone to use, you're correct.

    To recover some semblance of sanity, we could add some limitations. For instance (ha, ha), we could require the following properties to hold:

    • Anywhere such a function is used, the arguments given must match exactly one pattern. Anything else is a compiler error.
    • Adding new patterns (including by importing another module) should never change the meaning of valid code--either the same patterns are chosen, or a compiler error is produced.

    It should be clear that matching simple constructors like True or Nothing is straightforward. We also can handwave things a bit and assume that the compiler can disambiguate literals, like "bar" and "baz" above.

    On the other hand, binding arguments with patterns like (x, Just y) becomes awkward--writing such a pattern means giving up the ability to write patterns like (True, _) or (False, Just "foobar") later, since that would create ambiguity. Worse yet, pattern guards become nearly useless, because they need very general matches. Many common idioms will produce endless ambiguity headaches, and of course writing a "default" fall-through pattern is completely impossible.

    This is roughly the situation with type class instances.

    We could regain some expressive power by relaxing the required properties as such:

    • Anywhere such a function is used, it must match at least one pattern. No matches is a compiler error.
    • If a function is used such that multiple patterns match, the most specific pattern will be used. If there is no unique most specific pattern, an error is produced.
    • If a function is used in a way that matches a general instance, but could be applied at run-time to arguments that would match a more specific instance, this is a compiler error.

    Note that we are now in a situation where merely importing a module can change the behavior of a function, by bringing into scope a new, more specific pattern. Things might get murky in complicated cases involving higher-order functions, as well. Still, in many cases problems are unlikely--say, defining a generic fall-through pattern in a library, while letting client code add specific cases if needed.

    That's roughly where OverlappingInstances puts you. As suggested in the example above, if creating new overlaps is always either impossible or desired, and different modules won't end up seeing different, conflicting instances, then it's probably fine.

    What it really comes down to is that the limitations removed by OverlappingInstances are there to make using type classes behave sensibly under the "open world" assumption that any possible instance could later be added. By relaxing those requirements, you're taking on that burden yourself; so think through all the ways that new instances could be added and whether any of those scenarios are a significant problem. If you're convinced that nothing will break even in obscure and devious corner cases, then go ahead and use the extension.

    这篇关于OverlappingInstances有什么好的用例吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆