开放世界假设有什么好处?

发布于 2025-01-18 13:12:08 字数 1684 浏览 5 评论 0原文

作为一个(有点人为的)示例,假设我想使用泛型来确定类型何时无人居住(通过非底值)。我可以很好地完成大部分工作:

class Absurd a where
    absurd :: a -> b
    default absurd :: (Generic a, GAbsurd (Rep a)) => a -> b
    absurd = gabsurd . from
class GAbsurd f where
    gabsurd :: f p -> b
instance GAbsurd f => GAbsurd (M1 i c f) where
    gabsurd (M1 x) = gabsurd x
instance Absurd c => GAbsurd (K1 i c) where
    gabsurd (K1 x) = absurd x
instance GAbsurd V1 where
    gabsurd = \case
instance (GAbsurd a, GAbsurd b) => GAbsurd (a :+: b) where
    gabsurd (L1 x) = gabsurd x
    gabsurd (R1 x) = gabsurd x

当我需要为 :*: 定义一个实例时,问题就出现了。实现 GAbsurd (a :*: b) 只需要 GAbsurd aGAbsurd b 之一,但是我能做的最好的就是两者都需要,这太强了。例如:

data Void
data X = X Char Void

显然 X 是一个“荒谬”的类型,但这不能推断,因为虽然 Void 是荒谬的,但 Char 却不是,并且所以约束(Absurd Char, Absurd Void)无法解决。

我想做的是使用某种“约束析取”,如下所示:

instance (GAbsurd a || GAbsurd b) => GAbsurd (a :*: b) where
    gabsurd (x :*: y) = case eitherConstraint of
        LeftC  -> gabsurd x
        RightC -> gabsurd y

但是,在开放世界假设下, 这是不可能的。事实上,Data.Boring 中的代码(也包含 Absurd)完全忽略了这个问题:

GAbsurd (f :*: g) 有两个合理的实例,因此我们两者都没有定义

因此这引出了我的问题:为什么开放世界假设如此重要?我最好的猜测是它与孤立实例有关,但即便如此,GHC 不应该有所有导入模块的列表吗?

As a (somewhat contrived) example, say I want to use generics to figure out when a type is uninhabited (by a non-bottom value). I can get most of the way there just fine:

class Absurd a where
    absurd :: a -> b
    default absurd :: (Generic a, GAbsurd (Rep a)) => a -> b
    absurd = gabsurd . from
class GAbsurd f where
    gabsurd :: f p -> b
instance GAbsurd f => GAbsurd (M1 i c f) where
    gabsurd (M1 x) = gabsurd x
instance Absurd c => GAbsurd (K1 i c) where
    gabsurd (K1 x) = absurd x
instance GAbsurd V1 where
    gabsurd = \case
instance (GAbsurd a, GAbsurd b) => GAbsurd (a :+: b) where
    gabsurd (L1 x) = gabsurd x
    gabsurd (R1 x) = gabsurd x

The issue comes when I need to define an instance for :*:. Only one of GAbsurd a or GAbsurd b is required to implement GAbsurd (a :*: b), however the best I can do is to require both, which is too strong. For example:

data Void
data X = X Char Void

Obviously X is an "absurd" type, but that can't be inferred because although Void is absurd, Char is not and so the constraint (Absurd Char, Absurd Void) can't be solved.

What I would like to do is use some kind of "constraint disjunction" like this:

instance (GAbsurd a || GAbsurd b) => GAbsurd (a :*: b) where
    gabsurd (x :*: y) = case eitherConstraint of
        LeftC  -> gabsurd x
        RightC -> gabsurd y

However, under the open world assumption, that's not possible. In fact, the code in Data.Boring (which also holds Absurd) simply ignores the problem entirely:

There are two reasonable instances for GAbsurd (f :*: g), so we define neither

So this leads me to the question: why is the open world assumption so important? My best guess is that it has something to do with orphan instances, but even then shouldn't GHC have a list of all imported modules?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

好听的两个字的网名 2025-01-25 13:12:08

我不确定关键问题是孤儿实例和模块。

我认为主要问题是,面对约束析取时,要保持实例定义的连贯性和编译效率的连贯性是很难的。问题在于,您想象中的语法中的假设要么在编译时本地解决。例如,考虑函数:

foo :: (GAbsurd a) => GAbsurd (a :*: b)
foo = gabsurd

bar :: (GAbsurd b) => GAbsurd (a :*: b)
bar = gabsurd

这些都是类型纠正的。在每种情况下,都需要gabsurd(a:*:b)实例,并且可以通过gabsurd agabsurd b。但是,使用gabsurd(a:*:b)= gabsurd a的明显的编译时,实际实现这些功能的本地解决方案,用于第一个和gabsurd(a:*:*:*:*:*:*:*:*:b) )= gabsurd b第二个导致不一致。这不是这个特定示例的问题,但是如果这些是ord实例,并且我们使用一个多态函数将对象插入set和另一个多态函数,以将对象查看对象在那个set中,如果他们最终使用两种不同的ord实例,我们可能会遇到一个真正的问题。

为了保持连贯性,我们基本上必须随身携带许多其他类型的信息,以在选择实例时解决析取。而且,我们可能必须在传递这些信息和在运行时进行评估之间进行选择,或者为即使是非常琐碎的程序生成指数级的专业实例。

I'm not sure that the critical problem is orphan instances and modules.

I think the main issue is that it's unexpectedly difficult to maintain coherence of instance definitions and efficiency of compilation in the face of constraint disjunctions. The problem is that the hypothetical eitherConstraint in your imagined syntax can't be simply, locally resolved at compile time. For example, consider the functions:

foo :: (GAbsurd a) => GAbsurd (a :*: b)
foo = gabsurd

bar :: (GAbsurd b) => GAbsurd (a :*: b)
bar = gabsurd

These are both type-correct. In each case, a GAbsurd (a :*: b) instance is demanded, and one can be supplied via the disjuction either by GAbsurd a or GAbsurd b. However, the obvious compile-time, local solution to actually implementing these functions, of using gabsurd (a :*: b) = gabsurd a for the first and gabsurd (a :*: b) = gabsurd b for the second, leads to incoherence. It's not a problem for this specific example, but if these were Ord instances and we were using one polymorphic function to insert objects into a Set and another polymorphic function to look objects up in that Set, we could have a real problem on our hands if they ended up using two different Ord instances for the same type.

In order to maintain coherence, we'd basically have to carry a lot of additional type information around to resolve disjunctions when selecting an instance. And, we might have to choose between passing that information around and evaluating it at runtime or else generating an exponential number of specialized instances for even pretty trivial programs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文