Haskell 尝试折叠函数会导致不正确的类型
我有以下源代码:Haskell 尝试折叠函数会导致不正确的类型,haskell,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 T
{-# 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
然而,最终的目标是编写一个函数,该函数接受一个列表,并给出一个数据类型,该数据类型以其类型(本质上是一个向量)编码列表的长度:
您可能已经注意到缺少一些代码;它看起来很无聊,所以我把它放在了底部
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
问题是您想要将多态类型函数传递给
foldr
。请注意,foldr
本身具有多态类型,但它希望其参数具有单态类型
对于将多态函数作为参数的函数(实际上以多态方式使用这些参数函数),您需要所谓的高阶多态性。好消息是GHC支持更高级别的多态类型(使用RankNTypes
扩展);坏消息是类型推断对他们来说是不可判定的。因此,您需要在代码中使用显式类型签名,以便使类型检查器确信您的代码是正确的。当然,接下来的问题是,函数intToSize'
需要什么样的类型签名?还有更坏的消息:由于函数的类型需要依赖于您提供的整数值,因此不能直接用Haskell表示
也就是说,有了GADT和type系列,你可以朝着你似乎想要的方向走很长一段路:
{-# 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(such(such Zero))
与Three
相同
至于你更大的目标,写“一个函数,它接受一个列表,并给出一个数据类型,用它的类型编码列表的长度”:你根本不能这样做。根据在运行时之前可能不知道的值(列表的长度),不能有静态(即编译时)类型的函数 最接近的方法是将向量类型包装在存在包装器中:
{-# 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
的值,您就可以打开它,并对包含的向量进行各种处理,通过在其类型中编码其长度来增强类型安全性。“本质上,目标是获取一个Int并将其转换为上面定义的数据类型之一。”请提供一个具体的例子,包括输入和预期输出。“…因为函数的类型需要取决于您提供的整数值…”这基本上是我的全部问题。我不知道如何说服Haskell看整数并根据整数的值推断类型。@user2407038您基本上需要依赖类型。Haskell没有真正的依赖类型。你可以用GADT等来模拟它们。我在我的答案中添加了一个简单的例子。我看到了这个所谓的“增量数字”实现。不幸的是,这不是我所需要的;我没有六个不同的数字,我有10^10。它们是用十进制数字编码的。我不知道十进制数和递增数如何相互通信。@user2407038:您也可以在类型系统中对十进制数进行编码。请参阅,例如:“‘根据某个值,不能将的函数与静态类型(即编译时类型)一起使用…”我只需要返回类型属于某个类型类。当我试图写一个简明的例子时,我错误地忽略了这一点;我不知道这是否有什么不同。
{-# 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