是否可以编写函数Int - > NatSing n,NatSing是单身类型的peano数字?

时间:2013-11-05 22:51:18

标签: haskell types gadt

为模糊标题道歉,这里有一些背景:http://themonadreader.files.wordpress.com/2013/08/issue221.pdf

上述问题中的GADTs文章介绍了Nat类型和用于各种类型级列表函数的natSing类型(concat,!!,head,repeat等)。对于其中的几个函数,有必要创建用于定义+和<的类型族。关于Nat类型。

data Nat = Zero | Succ Nat

data NatSing (n :: Nat) where
    ZeroSing :: NatSing Zero
    SuccSing :: NatSing n -> NatSing (Succ n)

data List (n :: Nat) a where
    Nil  :: List n a
    Cons :: a -> List n a -> List (Succ n) a

无论如何,我编写了一个函数“list”,它将普通的[a]转换为List n a,以方便调用者。这需要列表的长度作为输入,很像repeat(来自链接文章):

list :: [a] -> NatSing n -> List n a
list []      ZeroSing    = Nil
list (x:xs) (SuccSing n) = Cons x (list xs n)
list _       _           = error "length mismatch"

使用辅助函数toNatSing :: Int -> NatSing n会很好,所以上面的内容可以写成

list :: [a] -> List n a
list xs = list' xs (toNatSing $ length xs)
  where
    list' :: [a] -> NatSing n -> List n a
    list' = ... -- same as before, but this time I simply "know" the function is no longer partial

是否可以编写这样的函数toNatSing?我一直在与类型摔跤,还没有想出任何东西。

非常感谢!

3 个答案:

答案 0 :(得分:6)

不,你不能写这样的功能。

类型

的功能
Int -> NatSing n

说它可以将任何整数转换为多态 NatSing。但是没有多态NatSing

您在此处想要的是让n确定Int。这是一种依赖类型:

(n :: Int) -> NatSing n

在Haskell中这样的事情是不可能的。您必须使用Agda或Idris或其他依赖类型的语言。对单身人士的黑客攻击正是哈斯克尔解决这个问题的方法。如果要根据值进行区分,则必须将值提升到类型级别,即NatSing

您可以通过将NatSing包装在存在类型中来编写一个为某些 n返回n的函数:

data ExNatSing where
  ExNatSing :: NatSing n -> ExNatSing

但这在实践中不会给你带来太多好处。通过打包n,您将丢失有关它的所有信息,并且以后无法根据它做出决定。

通过完全相同的参数,您也可以不希望定义函数

list :: [a] -> List n a

您可以用来节省一些打字工作的唯一方法是定义一个自动构造NatSing值的类型类:

class CNatSing (n :: Nat) where
  natSing :: NatSing n

instance CNatSing Zero where
  natSing = ZeroSing

instance CNatSing n => CNatSing (Succ n) where
  natSing = SuccSing natSing

然后,你可以说:

list :: CNatSing n => [a] -> List n a
list xs = list' xs natSing
  where
    list' :: [a] -> NatSing n -> List n a
    list' = ... -- same as before, but still partial

在这里,您使用此类型的上下文使GHC填入右侧NatSing。但是,此函数仍然部分,因为函数的调用者仍然可以选择n使用它。如果我想使用[Int]3作为List (Succ Zero) Int它将会崩溃。

同样,你可以把它包装在一个存在主义中:

data SomeList a where
  SomeList :: NatSing n -> List n a -> SomeList a

然后你可以写

list :: [a] -> SomeList a
list []       = SomeList ZeroSing Nil
list (x : xs) = case list xs of
                  SomeList n xs -> SomeList (SuccSing n) (x : xs')

同样,好处很小,但与ExNatSing相比,至少有一个:您现在可以暂时解包SomeList并将其传递给在{{1}上运行的函数,获得类型系统保证如何通过这些函数转换列表的长度。

答案 1 :(得分:2)

你想要的东西看起来像(Int - >(存在n.NatSing n)),其中n提前未知。你可以用(未经测试的):

之类的东西来做到这一点
data AnyNatSing where
  AnyNatSing :: NatSing n -> AnyNatSing

toNatSing :: Int -> AnyNatSing
toNatSing n
  | n > 0 = case toNatSing (n - 1) of
      AnyNatSing n -> AnyNatSing (SuccSing n)
  | otherwise = AnyNatSing ZeroSing

答案 2 :(得分:1)

路易斯·瓦瑟曼(Louis Wassermann)所说,与你想要的最接近的是一个存在主义的包装器,它使NatSing从外部变为单形。

我认为这很无用,因为它基本上只是抛弃长度的类型检查而你留下了一个愚蠢的标准整数类型。有更简单的方法,例如,使用愚蠢的标准整数类型和普通的Haskell列表......

但是有一种替代方案可能并不那么无用。请记住,如果您从函数返回一些值x,或者使用x传递更高阶和调用,则它几乎相同;我认为lisps特别喜欢这种延续传递技巧。

对于您的示例,您需要一个能够接受任何长度类型列表的函数。那么,这些功能肯定存在,例如,标量产品,需要两个长度相等的列表,但不关心长度是多少。然后它返回一个简单的单态数。为简单起见,让我们在列表类型上考虑更简单的sum

sumSing :: Num a => List (n::Nat) a -> a
sumSing Nil = 0
sumSing (Cons x ls) = x + sumSing ls

然后你可以这样做:

{-# LANGUAGE RankNTypes     #-}

onList :: (forall n . CNatSing n => List n a -> b) -> [a] -> b
f`onList`l = f (list l)

list是具有CNatSing类约束的kosmicus'变体)并将其称为

sumSing `onList` [1,2,3]

......当然,这本身并不比存在主义解决方案更有用(我认为,实际上这些解决方案实际上类似于这个RankN的东西)。但是你可以在这里做更多,比如标量产品示例 - 提供两个列表并实际确保通过类型系统它们具有相同的长度。这对于存在感来说会更加丑陋:你基本上需要一个单独的TwoEqualLenLists类型。