我有以下源代码:
{-# 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.但是,intToSize
和intToSize'
都不起作用; 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
答案 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
的值,就可以打开它并使包含的向量受到所有类型的处理,并且通过在其类型中对其长度进行编码来启用增强的类型安全性。