简体   繁体   English

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

[英]What is the easiest way to turn a list with known length into nested pairs in Haskell?

How should one convert a list with a known length into nested pairs? 如何将具有已知长度的列表转换为嵌套对? In other words, what is the most convenient way to fill the type holes below? 换句话说,填充下面的类型孔最方便的方法是什么?

_ [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)

EDIT: note that the type holes need not be the same function, I'm looking for a convenient pattern (if a convenient pattern exists) to fill the holes. 编辑:请注意,类型孔不必是相同的功能,我正在寻找一个方便的模式(如果存在方便的模式)来填补空洞。

Perhaps like this: 也许是这样的:

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

In ghci: 在ghci:

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

One may of course also directly implement one of these functions with pattern matching: 当然也可以通过模式匹配直接实现这些功能之一:

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

This will also not traverse the list as many times as there are elements, which is nice. 这也不会像有元素一样遍历列表,这很好。

Chiming in with a dependently typed version. 与依赖类型的版本一起使用。 First, let's get done with the boilerplate: 首先,让我们完成样板:

{-# 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) |])

The use of TH here is purely for boilerplate reduction and we won't use TH in our actual code. 这里使用TH纯粹是为了减少样板,我们不会在实际代码中使用TH。 In fact, the above could be (and should be) factored out in a package somewhere (at the time of writing this answer there isn't such a package with up-to-date singletons dependency). 实际上,上面的内容可以(并且应该)在某个地方的一个包中被考虑(在写这个答案时,没有这样一个包含最新singletons依赖的包)。

tuplify becomes a function whose return type depends on a Nat parameter. 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"

Trying it out: 尝试一下:

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

Writing out the naturals is quite arduous now, so let's introduce some syntactic sugar: 现在写出自然是非常艰巨的,所以让我们介绍一些语法糖:

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

type SN n = Sing (N n)

Now: 现在:

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

As a side note, if we convert the empty list to () (and thereby also allow one-element nested tuples) our definitions become much more natural: 作为旁注,如果我们将空列表转换为() (从而也允许单元素嵌套元组),我们的定义变得更加自然:

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)

This would be a nice exercise in Agda with dependent types. 这对于依旧类型的Agda来说是一个很好的练习。 In Haskell you can achieve something close with (also inspired from Daniel Wagner's solution) 在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)

Some tests: 一些测试:

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

Note that the return type annotation is mandatory, otherwise Haskell can not deduce how many elements the return tuple must have. 请注意,返回类型注释是必需的,否则Haskell无法推断返回元组必须具有的元素数量。 If there is a mismatch between the tuple and list length, a run-time error occurs. 如果元组和列表长度不匹配,则会发生运行时错误。 This is pretty much unavoidable since lists do not carry their length in their type, so the compiler can not check this earlier (unlike using a vector GADT). 这几乎是不可避免的,因为列表不会在其类型中携带它们的长度,因此编译器不能更早地检查它(与使用向量GADT不同)。

In order to have such a generic and type-safe function, you'd need dependent types so that the number of nested tuples in the result could depend on the length of the input list. 为了拥有这样的泛型和类型安全函数,您需要依赖类型,以便结果中嵌套元组的数量可能取决于输入列表的长度。

However it's possible to get close to that with polymorphic recursion . 然而,有可能接近多态递归

Let's define a data type as follows: 让我们定义一个数据类型如下:

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

type TupleList = TupleList' ()

So a value of type TupleList a is isomorphic to () , ((), a) , (((), a), a) etc, depending on how many Tuple constructors wrap the final Value . 因此,类型TupleList a的值与()((), a)(((), a), a) TupleList a构,这取决于有多少Tuple构造函数包装最终的Value

Now we can convert a list into such a tuple as follows: 现在我们可以将列表转换为如下的元组:

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)

Notice that loop uses polymorphic recursion (as any function that operates on TupleList' - its recursive call has signature (r, a) -> [a] -> TupleList' (r, a) a . 请注意, loop使用多态递归(作为对TupleList'进行操作的任何函数TupleList' - 其递归调用具有签名(r, a) -> [a] -> TupleList' (r, a) a

Example: mapM_ (print . fromList) (inits [1..4]) yields 示例: 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)))))

The simplest way is 最简单的方法是

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

But toTuples returns pairs in the reverse order: toTuples以相反的顺序返回对:

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

We can use CPS to fix this: 我们可以使用CPS来解决这个问题:

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

Then 然后

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

And we can define some syntactic sugar (I'm mostly stealing from András Kovács' answer): 我们可以定义一些句法糖(我主要是从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)

Note that the code works for infinite lists too, since there is no reversing. 请注意,代码也适用于无限列表,因为没有反转。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM