在类型级别验证

时间:2015-01-24 22:45:57

标签: validation haskell type-families

假设我想在没有外部工具(如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

我该如何解决?

2 个答案:

答案 0 :(得分:4)

编写的validate函数在当前的GHC中不可用。看它的类型签名:

validate :: Validated a => Underlying a -> Bool

您可以合理地认为,给定Underlying a类型的值,可以找出要使用的Validated个实例,即a个实例。但这是一个错误:由于Underlying不是单射的,因此可能存在bc类型Underlying b ~ Underlying c;因此,bc都不能成为使用哪个实例的规范选择。也就是说, 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!
相关问题