尝试折叠功能会导致类型错误

时间:2013-06-03 22:47:03

标签: haskell

我有以下源代码:

{-# LANGUAGE 
    FlexibleContexts,
    MultiParamTypeClasses,
    FunctionalDependencies
    #-} 

class Digit d  

data Zero
data One
data Two
data Three
data Four
data Five
data Six

instance Digit Zero
instance Digit One
instance Digit Two
instance Digit Three
instance Digit Four
instance Digit Five
instance Digit Six


class Sum a b c | a b -> c, a c -> b, c b -> a 

incSize :: (Digit z, Digit x, Sum x One z) => x -> z --written by me
incSize _ = undefined

intToSize :: (Digit x, Num a, Sum x One x) => a -> x -> t --inferred by GHCI
intToSize n v = intToSize (n-1) (incSize v)

intToSize' :: (Digit b, Sum b One b) => Int -> t -> b -> b --inferred by GHCI
intToSize' n v = foldr (.) id (replicate n incSize)

intToSize'' :: (Digit a, Digit b1, Digit b, Digit c, Sum a One b1, Sum b1 One b, Sum b One c) => a -> c --inferred by GHCI
intToSize'' = incSize . incSize . incSize 

基本上,我们的目标是获取Int并将其转换为上面定义的数据类型之一。函数incSize正常工作;它将大小增加1。函数inToSize''也有效;它会将给定数字增加3.但是,intToSizeintToSize'都不起作用; GHCI如上所述推断其类型(显然,a + 1 = / = a)。我不确定为什么手动编写组合正常工作,而其他任何事情都会失败(我认为这些函数不能正常编译,但每次使用时都会发出错误。)使用应该如下所示:

> :t intToSize'' (undefined :: Zero)
> intToSize'' (undefined :: Zero) :: Three

然而,最终的目标是编写一个获取列表的函数,并提供一个数据类型,该数据类型在其类型(本质上是向量)中编码列表的长度:

data Vect s v = Vect v
instance forall s v . Show (Vect s v) where
     --toInt is a function which takes a type number and returns its data level value
     show (Vect v) = "Vect " ++ (show . toInt) (undefined :: s) ++ show v  

> let l = [1,2,3,4,5]
> vector l 
> Vect 5 [1,2,3,4,5] 

您可能已经注意到某些代码丢失了;看起来相当无聊,而不是把它包含在底部。

instance Sum  Zero  Zero  Zero
instance Sum  Zero  One   One
instance Sum  Zero  Two   Two
instance Sum  Zero  Three Three
instance Sum  Zero  Four  Four
instance Sum  Zero  Five  Five
instance Sum  Zero  Six   Six
instance Sum  One   Zero  One
instance Sum  One   One   Two
instance Sum  One   Two   Three
instance Sum  One   Three Four
instance Sum  One   Four  Five
instance Sum  One   Five  Six
instance Sum  Two   Zero  Two
instance Sum  Two   One   Three
instance Sum  Two   Two   Four
instance Sum  Two   Three Five
instance Sum  Two   Four  Six
instance Sum  Three Zero  Three
instance Sum  Three One   Four
instance Sum  Three Two   Five
instance Sum  Three Three Six
instance Sum  Four  Zero  Four
instance Sum  Four  One   Five
instance Sum  Four  Two   Six
instance Sum  Five  Zero  Five
instance Sum  Five  One   Six
instance Sum  Six   Zero  Six

1 个答案:

答案 0 :(得分:10)

问题是您希望将多态类型的函数传递给foldr。请注意,虽然foldr本身具有多态类型,但它期望其参数具有单态类型。

对于将多态函数作为参数(实际上以多态方式使用这些参数函数)的函数,您需要所谓的更高级多态。好消息是GHC支持排名较高的多态类型(使用RankNTypes扩展名);坏消息是类型推断对他们来说是不可判定的。因此,您需要在代码中使用显式类型签名,以便说服类型检查器您的代码是正确的。那么问题当然会产生你的函数intToSize'所需的类型签名?然后还有更多的坏消息:因为你的函数类型需要依赖于你提供的整数值,所以它不能直接在Haskell中表达。


那就是说,对于GADT和类型系列,你可以走很远的路,看看你的目标:

{-# LANGUAGE GADTs        #-}
{-# LANGUAGE TypeFamilies #-}

data Zero
data Succ n

type One   = Succ Zero
type Two   = Succ One
type Three = Succ Two

data Nat :: * -> * where
  Zero :: Nat Zero
  Succ :: Nat n -> Nat (Succ n)

zero  = Zero
one   = Succ zero
two   = Succ one
three = Succ two

type family Sum m n          :: *
type instance Sum Zero n     =  n
type instance Sum (Succ m) n =  Succ (Sum m n)

add            :: Nat m -> Nat n -> Nat (Sum m n)
add Zero n     =  n
add (Succ m) n =  Succ (add m n)

incSize :: Nat m -> Nat (Sum One m)
incSize = add one  -- or just: Succ 

例如:

> :t incSize two
incSize two :: Nat (Sum One (Succ (Succ Zero)))

并注意Sum One (Succ (Succ Zero)))Three相同。


至于你更大的目标,写一个“获取列表的函数,并给出一个数据类型,它在类型中编码列表的长度”:你根本不能这样做。您不能使用 static (即编译时)类型的函数,具体取决于在运行时可能未知的值(列表的长度)。

最接近的是将矢量类型包装在存在包装器中:

{-# LANGUAGE DataKinds    #-}
{-# LANGUAGE GADTs        #-}
{-# LANGUAGE TypeFamilies #-}

data Nat = Zero | Succ Nat

data Vec :: Nat -> * -> * where
  Nil  :: Vec Zero a
  Cons :: a -> Vec n a -> Vec (Succ n) a

data EVec :: * -> * where
  Exists :: Vec n a -> EVec a

enil :: EVec a
enil =  Exists Nil

econs               :: a -> EVec a -> EVec a
econs x (Exists xs) =  Exists (Cons x xs)

vector :: [a] -> EVec a
vector =  foldr econs enil

现在,只要您具有类型EVec的值,就可以打开它并使包含的向量受到所有类型的处理,并且通过在其类型中对其长度进行编码来启用增强的类型安全性。