2 回答
这里有几件事。如果你编译-Wall
(你应该!)你会发现你的定义foo
给出了一个不详尽的模式警告。它应该,因为你定义的方式AorB
是“开放的”。也就是说,另一个模块中的某人可以自由声明
instance AorB 'C
然后您的定义foo
将突然变得无效,因为它无法处理SomeC
此案。要获得您正在寻找的东西,您应该使用封闭类型系列:
type family IsAorB s where
IsAorB 'A = 'True
IsAorB 'B = 'True
IsAorB _ = 'False
这个族完全定义在State
s 上。然后,我们将像这样定义您之前的AorB
约束:
type AorB s = IsAorB s ~ 'True
但是,稍后我们将需要以AorB
柯里化形式使用,这对于类型同义词是不允许的。有一个用于声明 curriable 同义词的习语有点笨拙,但这是我们目前最好的:
class (IsAorB s ~ 'True) => AorB s
instance (IsAorB s ~ 'True) => AorB s
不管怎样,有了这个新定义,你会发现不详尽的模式警告消失了foo
。好的。
现在回答你的问题。您的定义的问题(forall
为了清楚起见,明确添加了)
bar :: forall s. AorB s => Some s
bar = SomeA
是允许我们实例化bar @B
,给我们
bar @B :: AorB B => Some B
bar = SomeA
AorB B
是可满足的,所以我们应该能够得到 a Some B
,对吧?但不是根据你的实现。所以这里在逻辑上是错误的。您可能希望返回一个存在量化 s
的 ,换句话说,您希望bar
函数选择s
它,而不是调用者。非正式地
bar :: exists s. AorB s <AND> Some s
也就是说,bar
选择一个s
,并返回一个Some s
,以及一些AorB s
成立的证据。这不再是一个暗示,我们不会让调用者负责证明bar
选择的类型是A
或者B
——调用者不知道选择了什么。
Haskell 不直接支持存在类型,但我们可以使用 GADT 对它们进行建模(确保使用PolyKinds
and ConstraintKinds
)
data Ex c f where
Ex :: c a => f a -> Ex c f
Ex c f
可以读作“存在a
这样的c a
存在并且存在价值f a
”。这是存在的,因为变量a
没有出现在 中Ex c f
,它被构造函数隐藏了。现在我们可以实现bar
bar :: Ex AorB Some
bar = Ex SomeA
我们可以通过将其传递给您的来测试约束是否正确传播foo
:
test :: Bool
test = case bar of Ex s -> foo s
你去吧。
也就是说,在设计方面我只想说
bar :: Some A
反而。类型签名应尽可能提供信息。不要隐藏你知道的信息——让抽象来隐藏。当您隐藏有关您的假设/论点的信息时,您正在使您的签名更强大;当你隐瞒你的结果时,你正在使它变得更弱。让它强大。
以下是基于@luqui 回答的完整工作代码,供参考:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}
module Test2 where
data State = A | B | C
type family IsAorB (s :: State) where
IsAorB 'A = 'True
IsAorB 'B = 'True
IsAorB _ = 'False
-- type AorB s = IsAorB s ~ 'True
class (IsAorB s ~ 'True) => AorB s
instance (IsAorB s ~ 'True) => AorB s
data Some (s :: State) where
SomeA :: Some 'A
SomeB :: Some 'B
SomeC :: Some 'C
data Ex c f where
Ex :: c a => f a -> Ex c f
bar :: Ex AorB Some
bar = Ex SomeA