假设我想在没有外部工具(如LiquidHaskell)的帮助下构建满足某些不变量的子类型(理想情况下,即使没有类型类,我也希望这样做)。最优雅的方式是什么?到目前为止,我尝试了以下内容:
class Validated a where
type Underlying a
validate :: Underlying a -> Bool
construct :: Underlying a -> a
use :: a -> Underlying a
makeValidated :: Validated a => Underlying a -> Maybe a
makeValidated u = if validate u
then Just (construct u)
else Nothing
newtype Name = Name String
instance Validated Name where
type Underlying Name = String
validate str = and [ isUppercase (str !! 0 )
, all isLetter str ]
construct = Name
use (Name str) = str
我认为如果我不输出"姓名"来自模块的构造函数,我将有一个有效的解决方案,因为构造该类型元素的唯一方法是通过makeValidated函数。
然而编译器抱怨如下:
Could not deduce (Underlying a0 ~ Underlying a)
from the context (Validated a)
bound by the type signature for
makeValidated :: Validated a => Underlying a -> Maybe a
at validated.hs:11:18-55
NB: `Underlying' is a type function, and may not be injective
The type variable `a0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
In the first argument of `validate', namely `u'
In the expression: validate u
In the expression:
if validate u then Just (construct u) else Nothing
我该如何解决?
答案 0 :(得分:4)
编写的validate
函数在当前的GHC中不可用。看它的类型签名:
validate :: Validated a => Underlying a -> Bool
您可以合理地认为,给定Underlying a
类型的值,可以找出要使用的Validated
个实例,即a
个实例。但这是一个错误:由于Underlying
不是单射的,因此可能存在b
和c
类型Underlying b ~ Underlying c
;因此,b
和c
都不能成为使用哪个实例的规范选择。也就是说, 对F
始终为真的类型没有好的映射F (Underlying a) ~ a
!
另一种方法是使用数据系列而不是类型系列。
class Validated a where
data Underlying a
validate :: Underlying a -> Bool
instance Validated Name where
data Underlying Name = Underlying String
validate (Underlying name) = ...
答案 1 :(得分:4)
Underlying
是一个类型函数,可能不是单射的。那就是:
instance Validate T1 where
type Underlying T1 = Int
validate = ... -- code A
instance Validate T2 where
type Underlying T2 = Int
validate = ... -- code B
现在考虑
validate (42 :: Int)
这应该怎么办?它应该调用代码A
还是B
?自Underlying T1 = Underlying T2 = Int
以来,无法分辨。
无法明确地致电validate
。为避免这种情况,可能的解决方法是在验证函数中添加“代理”参数:
data Proxy a = Proxy
class Validate a where
validate :: Proxy a -> Underlying a -> Bool
现在你可以使用:
validate Proxy (42 :: Int) -- still ambiguous!
validate (Proxy :: Proxy T1) (42 :: Int) -- Now OK!
validate (Proxy :: Proxy T2) (42 :: Int) -- Now OK!