我试图将函数的返回类型限制为(实物)构造函数的子集。我可以使用typeclasses来限制输入类型,但是当我对返回类型尝试如下所示的相同技术时,会出现Couldn't match type ‘s’ with ‘'A’
错误。
是否有一种方法可以限制下面的bar
函数以返回SomeA
或SomeB
?
Liquid Haskell似乎是一种替代方案,但似乎只能使用DataKinds
,GADTs
和/或TypeInType
之类的东西来实现。
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
module Test where
data State = A | B | C
class AorB (s :: State)
instance AorB 'A
instance AorB 'B
data Some (s :: State) where
SomeA :: Some 'A
SomeB :: Some 'B
SomeC :: Some 'C
-- This works
foo :: AorB s => Some s -> Bool
foo aorb = case aorb of
SomeA -> True
SomeB -> False
-- This fails to compile with ""Couldn't match type ‘s’ with ‘'A’""
bar :: AorB s => Some s
bar = SomeA
答案 0 :(得分:4)
这里有几件事。如果使用-Wall
(应该!)进行编译,则会发现foo
的定义给出了详尽的模式警告。而且应该这样,因为您定义AorB
的方式是“开放的”。也就是说,另一个模块中的某个人可以自由声明
instance AorB 'C
,然后您对foo
的定义将突然变得无效,因为它无法处理SomeC
的情况。要获得所需的内容,您应该使用封闭类型族:
type family IsAorB s where
IsAorB 'A = 'True
IsAorB 'B = 'True
IsAorB _ = 'False
该家族完全在State
上定义。然后,我们将像这样定义您之前的AorB
约束:
type AorB s = IsAorB s ~ 'True
但是,稍后我们需要以咖喱形式使用AorB
,类型同义词不允许使用。有一个声明可讽刺的同义词的习惯用法,有点笨拙,但这是我们目前拥有的最好的习惯:
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
是可以满足的,所以我们应该能够得到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
和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
相反。类型签名应尽可能提供更多信息。不要隐藏您知道的信息-让抽象进行隐藏。当您隐藏有关您的假设/论据的信息时,您的签名就会更强;当您隐藏搜索结果时,就是说它变得更弱。使其坚固。
答案 1 :(得分:1)
这是基于@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