在Haskell中将具有已知长度的列表转换为嵌套对的最简单方法是什么?

时间:2015-04-23 23:15:59

标签: list haskell tuples

如何将具有已知长度的列表转换为嵌套对?换句话说,填充下面的类型孔最方便的方法是什么?

_ [1,2]       :: (Int,Int)
_ [1,2,3]     :: ((Int,Int),Int)
_ [1,2,3,4]   :: (((Int,Int),Int),Int)
_ [1,2,3,4,5] :: ((((Int,Int),Int),Int),Int)

编辑:请注意,类型孔不必是相同的功能,我正在寻找一个方便的图案(如果存在方便的图案)来填补这些洞。

5 个答案:

答案 0 :(得分:7)

也许是这样:

step f xs = (f (init xs), last xs)
len1 = head
len2 = step len1
len3 = step len2
len4 = step len3

在ghci:

*Main> len4 [1..4]
(((1,2),3),4)

当然也可以通过模式匹配直接实现其中一个功能:

len4' [a,b,c,d] = (((a,b),c),d)

这也不会像有元素那样多次遍历列表,这很好。

答案 1 :(得分:5)

使用依赖类型的版本进行定制。首先,让我们完成样板:

{-# LANGUAGE
  TemplateHaskell, DataKinds, ScopedTypeVariables,
  FlexibleInstances, PolyKinds, TypeOperators,
  TypeFamilies, GADTs, UndecidableInstances #-}

import Data.Singletons.TH
import qualified GHC.TypeLits as Lit

$(singletons [d| data Nat = Z | S Nat deriving (Eq, Show) |])

这里使用TH纯粹是为了减少样板,我们不会在实际代码中使用TH。实际上,上面的内容可以(并且应该)在某个地方的一个包中被考虑(在写这个答案时,没有这样的包具有最新的singletons依赖性)。

tuplify成为一个函数,其返回类型取决于Nat参数。

type family NTup n a where
  NTup (S (S Z))     a = (a, a)
  NTup (S (S (S n))) a = (NTup (S (S n)) a, a)

tuplify :: Sing n -> [a] -> NTup n a
tuplify n as = go n (reverse as) where
  go :: Sing n -> [a] -> NTup n a
  go (SS (SS SZ))     [a, b] = (b, a)
  go (SS (SS (SS n))) (a:as) = (go (SS (SS n)) as, a)
  go _                _      = error "tuplify: length mismatch"

尝试一下:

tuplify (SS (SS (SS SZ))) [1, 2, 3] -- ((1, 2), 3)

现在写出自然是非常艰难的,所以让我们介绍一些语法糖:

type family N n where
  N 0 = Z
  N n = S (N (n Lit.- 1))

type SN n = Sing (N n)

现在:

tuplify (sing:: SN 10) [1..10] -- (((((((((1,2),3),4),5),6),7),8),9),10)

作为旁注,如果我们将空列表转换为()(从而也允许单元素嵌套元组),我们的定义会变得更加自然:

type family NTup n a where
  NTup Z     a = ()
  NTup (S n) a = (NTup n a, a)

tuplify :: Sing n -> [a] -> NTup n a
tuplify n = go n . reverse where
  go :: Sing n -> [a] -> NTup n a
  go SZ     []     = ()
  go (SS n) (a:as) = (go n as, a)
  go _      _      = error "tuplify: length mismatch"

tuplify (sing:: SN 5) [1..5] -- ((((((),1),2),3),4),5)

答案 2 :(得分:4)

这对Agda来说是一个很好的练习,具有依赖类型。在Haskell中你可以获得一些接近的东西(也受到Daniel Wagner的解决方案的启发)

class C a b where
   listToTuple :: [a] -> b

instance C a a where
   listToTuple [x] = x

instance C a b => C a (b,a) where
   listToTuple xs = (listToTuple (init xs), last xs)

一些测试:

> listToTuple [1..3::Int] :: ((Int,Int),Int)
((1,2),3)
> listToTuple [0..3::Int] :: (((Int,Int),Int),Int)
(((0,1),2),3)

请注意,返回类型注释是必需的,否则Haskell无法推断返回元组必须具有的元素数量。如果元组和列表长度不匹配,则会发生运行时错误。这几乎是不可避免的,因为列表在它们的类型中没有长度,因此编译器不能更早地检查它(与使用向量GADT不同)。

答案 3 :(得分:4)

为了拥有这样的泛型和类型安全函数,您需要dependent types,以便结果中嵌套元组的数量可能取决于输入列表的长度。

然而,polymorphic recursion可以接近它。

让我们按如下方式定义数据类型:

data TupleList' r a = Value r | Tuple (TupleList' (r, a) a)
  deriving (Show, Read, Eq, Ord)

type TupleList = TupleList' ()

因此TupleList a类型的值与()((), a)(((), a), a)等同构,具体取决于Tuple个构造函数包装最终Value fromList :: [a] -> TupleList a fromList = loop () where loop :: r -> [a] -> TupleList' r a loop r [] = Value r loop r (x:xs) = Tuple (loop (r, x) xs) 1}}。

现在我们可以将列表转换为如下的元组:

loop

请注意TupleList'使用多态递归(与(r, a) -> [a] -> TupleList' (r, a) a上运行的任何函数一样 - 其递归调用具有签名mapM_ (print . fromList) (inits [1..4])

示例:Value () Tuple (Value ((),1)) Tuple (Tuple (Value (((),1),2))) Tuple (Tuple (Tuple (Value ((((),1),2),3)))) Tuple (Tuple (Tuple (Tuple (Value (((((),1),2),3),4))))) 产生

Boolean isClicked = false;

答案 4 :(得分:2)

最简单的方法是

z   (x:xs) = x
s r (x:xs) = (x, r xs)
toTuples n xs = n xs

但是toTuples以相反的顺序返回对:

 toTuples (s (s (s z))) [1..] == (1,(2,(3,4)))

我们可以使用CPS来解决这个问题:

z   f  xs    = f ()
s r f (x:xs) = r (\p -> (f p, x)) xs
toTuples n (x:xs) = n (const x) xs

然后

toTuples (s (s (s z))) [1..] == (((1,2),3),4)

我们可以定义一些句法糖(我主要是从AndrásKovács的回答中窃取):

{-# LANGUAGE TemplateHaskell, UndecidableInstances, DataKinds, GADTs, TypeFamilies, TypeOperators #-}

import Data.Singletons.TH
import GHC.TypeLits

$(singletons [d| data Nat = Z | S Nat deriving (Eq, Show) |])

z   f  xs    = f ()
s r f (x:xs) = r (\p -> (f p, x)) xs

toTuples n (x:xs) = n (const x) xs

type family Result n r a where
  Result  Z    r a = r
  Result (S n) r a = Result n (r, a) a

run :: Sing n -> (() -> r) -> [a] -> Result n r a
run  SZ     = z
run (SS sn) = s (run sn)

toTuplesN :: Sing n -> [a] -> Result n a a
toTuplesN sn (x:xs) = run sn (const x) xs

type family N n where
  N 0 = Z
  N n = S (N (n - 1))

type SN n = Sing (N (n - 1))

main = print $ toTuplesN (sing :: SN 6) [1..] -- (((((1,2),3),4),5),6)

请注意,该代码也适用于无限列表,因为没有反转。