如何在Haskell中将列表转换为元组?

32

在Haskell中,我如何最好地将列表转换为元组:

[1,2,3,4,5,6] -> (1,2,3,4,5,6)
8个回答

53

一般情况下,是无法做到的。每个元组大小都是一种不同的类型,而长度任意的列表则是单一类型。因此,很难编写一个函数,它可以接受一个列表并返回相同长度的元组——这样的函数将没有明确定义的返回类型。

比如,你可以有这样的函数:

tuplify2 :: [a] -> (a,a)
tuplify2 [x,y] = (x,y)

tuplify3 :: [a] -> (a,a,a)
tuplify3 [x,y,z] = (x,y,z)

...但是没有一种同时完成这两个任务的方法。

可以使用各种元编程技术编写一个通用版本,但你很少会这样做。

请注意,同样的问题也适用于其他情况,例如为不同的元组编写类实例 - 查看标准库中Data.Tuple的源代码


那怎么可能是 Data.Tuple 的真正源代码呢?我的意思是... 导出列表中的第二个元素是:, snd -- :: (a,b) -> a。这个类型上的明显错误真的存在吗? - Bakuriu
2
@Bakuriu:哈哈,这是一个有趣的打字错误。当然,它只是一个注释 - 文件末尾snd的类型签名是正确的。那也是一个相当旧的版本的Data.Tuple,更新的版本没有这个错误。 - C. A. McCann

27

如果你想提取可变数量的元素,并且由于类型检查,(a,b)和(a,b,c)具有不同的类型,那么Template Haskell是你能够接近的最好方法。

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
tuple :: Int -> ExpQ
tuple n = do
    ns <- replicateM n (newName "x")
    lamE [foldr (\x y -> conP '(:) [varP x,y]) wildP ns] (tupE $ map varE ns)

那么:

$(tuple 6) [1,2,3,4,5,6] == (1,2,3,4,5,6)
$(tuple 3) "abc" == ('a','b','c')

总的来说,如果你需要这个答案,那么你可能在某个地方问错了问题。

如果你只是想要平面的随机访问,也许更好的选择是使用一个数组。


4
Template Haskell 很棒,但我希望它能更易读一些。不过,也许最好不要这样 -- 否则人们会过度使用它。 - Benjamin Kovach
显然,Control.Lensmap (^..each) 更接近,它也适用于任意元组。——并不是所有“针对任意元组”的东西都与元组的整个目的有关。 (这就是列表的作用。当需要防止这种用法时,才会使用元组。) - anon
由于each使用惰性模式的方式:ghci> undefined & partsOf each .~ [1,2,3,4] :: (Int,Int,Int,Int) 会产生(1,2,3,4)。因此,您可以双向操作,直到大小约为9。当然,您需要知道结果元组类型。 - Edward Kmett

16

我感觉好像要建议你把枪指向你的脚,然后相信你不会开枪。

> list2Tuple lst = read $ "(" ++ (init.tail.show) lst ++ ")"
> list2Tuple [1,2,3] :: (Int, Int, Int)
(1,2,3)
> list2Tuple [1,2,3,4] :: (Int, Int, Int, Int)
(1,2,3,4)

这将适用于 Show 和 Read 定义的任何元组长度。


这个真的是在思考出奇制胜了。 - gust

8
元组和列表是非常不同的东西。你最好手动编写一个转换函数:
```python def tuple_to_list(t): return list(t) ```
这个函数将元组`t`转换为列表。
toTuple :: [a] -> (a,a,a,a,a,a)
toTuple [a,b,c,d,e,f] = (a,b,c,d,e,f)

注意类型的不同:列表中的单个变量扩展为元组的六个变量。因此,您需要为每个元组大小编写一个函数。


1
你和camccann打败了我。这就是他最好的办法,只能使用N个函数,然后使用处理程序调用其中一个函数来执行给定的N。 - Spidey
元组在数学中是列表。它们非常相似。在Haskell的实现中,元组具有固定的类型长度,而列表则没有,因此将两者转换并不容易,但这是一种语言特定的实现问题。 - clay

5

我发现很难清楚地解释模板Haskell操作,但这里有一个演示:

> :m +Language.Haskell.TH
> :set -XTemplateHaskell
> runQ [| [1,2,3,4,5,6] |] >>= putStrLn . pprint
[1, 2, 3, 4, 5, 6]
> runQ [| [1,2,3,4,5,6] |] >>= \ (ListE exps) -> putStrLn (pprint (TupE exps))
(1, 2, 3, 4, 5, 6)

3
我不认为在Haskell中实现这一点是可能的,对于一个在编译时不知道任意长度列表的情况。模板Haskell无法做到这一点,因为它仅在编译时操作。我遇到了需要精确执行此操作的情况,但我必须绕过它。一个数据库库期望查询参数为不同长度的元组,但我有一个任意长度的列表。所以我不得不绕过库接口。如果它可以接受一个列表就好了。 基本上问题在于不同长度的元组是不同类型的。但是Haskell编译器必须在编译时知道运行时可能存在的类型。在运行时将任意长度的列表转换为元组可能会创建一些编译时未知的类型。

2
如果使用准引用,实际上可以比手动编写每个元组大小的函数更好,这里有详细描述。但是,如果您预计要通用地使用此代码,我会感到不安。

希望今天稍后有时间时能够编写一个小例子,并在我的评论中进行编辑... - StevenC
2
准引用是模板Haskell的一部分,我相信这就是我所提到的元编程。最终,这仍然会为每个元组大小生成一个函数,只是它会编写编译时代码来代替手动编写。我自己还没有掌握TH的技巧,所以我期待着看到你的示例! - C. A. McCann
Edward Kmett比我先完成了它。我以为我之前已经用过quasiquotes完成了它,但我想那只是TH。抱歉。 - StevenC

0

在处理命令行参数时,您可以使用getArgs函数,该函数将为您提供一个字符串列表:

getArgs :: IO [String] 

链接: https://hackage.haskell.org/package/base-4.16.0.0/docs/System-Environment.html#v:getArgs

当我处理命令行参数时,我更喜欢使用元组而不是列表,因此我将列表转换为元组。请参见下面的代码:

import System.Environment

main = do
    args <- getArgs
    let (a:b:c) = args
    print a

调用程序(在PowerShell中):

PS C:\Haskell> runghc convertListToTuple goodday to you
"goodday"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接