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