【问题标题】:Haskell (:) and (++) differencesHaskell (:) 和 (++) 的区别
【发布时间】:2010-12-21 12:45:19
【问题描述】:

对于这样的问题,我很抱歉。我不太清楚 Haskell 中 :++ 运算符的区别。

x:y:[] = [x,y]  

还有

[x] ++ [y] = [x,y]

至于我提出这个问题的反向功能,

reverse ::[a]->[a]
reverse [] = []
reverse (x:xs) = reverse(xs)++[x]

为什么以下方法不起作用?

reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs):x:[]

给出类型错误。

【问题讨论】:

  • 附带说明,您可以(并且应该)在不使用括号的情况下调用:reverse (x:xs) = reverse xs ++ [x],否则当您使用具有多个参数的函数时会被绊倒。
  • 不要调用像func(arg)这样的函数。那是可怜的哈斯克尔。总是调用像func arg 这样的函数。带有清晰空格的代码使代码更加自信和可读。
  • @AJFarmar func arg 确实比 func(arg) 更正确 Haskell,但我认为 f(x) 通常比 f x 更具可读性,因为它与大多数其他语言以及指定函数的数学方法。所以我想说“更自信、更易读”是一个见仁见智的问题。
  • @icc97,上下文。在编写 Haskell 代码的上下文中,func(arg) 与使用 "'goes down to' operator" in C 一样令人困惑

标签: list haskell syntax


【解决方案1】:

您可以稍作更改并获得正确的结果。

reversex ::[Int]->[Int] # comment this line
reversex [] = []
reversex (x:xs) = reversex(xs) ++ x : []

【讨论】:

    【解决方案2】:

    cons 往往是类型构造函数而不是运算符。这里的例子是: 可以在let..in.. 表达式中使用,但++ 不是

    let x : xs = [1, 2, 3] in x -- known as type deconstructing
    

    将返回 1 但

    let [x] ++ [y, z] = [1, 2, 3] in x
    

    将返回错误Variable not in scope x

    为方便起见,请像这样考虑cons

    data List a = Cons a (List a) -- is equvalent with `data [a] = a:[a]`
    

    https://en.wikibooks.org/wiki/Haskell/Other_data_structures

    此外,如果您想使用 cons 反转数组。这里举个例子,知识取自Prolog

    import Data.Function
    
    reversex1 [] = []
    reversex1 arr = reversex arr []
    
    reversex [] arr = arr
    reversex (x:xs) ys = reversex xs (x:ys)
    
    main = do
        reversex1 [1..10] & print
    

    【讨论】:

      【解决方案3】:

      : 运算符称为“cons”运算符,用于将头元素添加到列表中。所以[] 是一个列表,x:[]x 添加到空列表前面,从而形成列表[x]。如果你然后 cons y:[x] 你最终得到列表 [y, x]y:x:[] 相同。

      ++ 运算符是列表连接运算符,它将两个列表作为操作数并将它们“组合”成一个列表。因此,如果您有列表[x] 和列表[y],那么您可以像这样连接它们:[x]++[y] 得到[x, y]。

      注意: 接受一个元素和一个列表,而++ 接受两个列表。

      至于你的代码不起作用。

      reversex ::[Int]->[Int]
      reversex [] = []
      reversex (x:xs) = reversex(xs):x:[]
      

      reverse 函数的计算结果是一个列表。由于: 运算符不将列表作为其第一个参数,因此reverse(xs):x 无效。但是reverse(xs)++[x] 是有效的。

      【讨论】:

        【解决方案4】:

        与 (++) 的连接

        也许我正在考虑深入研究,但是, 据我了解,如果您尝试连接 例如使用(++) 的列表:

        [1, 2, 3] ++ [4, 5]
        

        (++) 必须遍历完整的左侧列表。 看看code of (++)就可以了 更加清晰。

        (++) :: [a] -> [a] -> [a]
        (++) []     ys = ys
        (++) (x:xs) ys = x : xs ++ ys
        

        因此,最好避免使用(++),因为每次调用 reverse(xs)++[x] 列表越来越大(或越来越小,取决于 上的观点。无论如何,程序只需要遍历另一个 每次通话都列出)

        示例:

        假设我通过串联实现了反向。

        reversex ::[Int]->[Int]
        reversex [] = []
        reversex (x:xs) = reversex(xs)++[x]
        

        反转列表 [1, 2, 3, 4] 看起来有点像这样:

        reversex [1, 2, 3, 4]
        reversex [2, 3, 4]               ++ [1]
        reversex [3, 4]           ++ [2] ++ [1]
        reversex [4]       ++ [3] ++ [2] ++ [1]
        reversex [] ++ [4] ++ [3] ++ [2] ++ [1]
                 [] ++ [4] ++ [3] ++ [2] ++ [1]
                 [4]       ++ [3] ++ [2] ++ [1]
                 [4, 3]           ++ [2] ++ [1]
                 [4, 3, 2]               ++ [1]
                 [4, 3, 2, 1]
        

        使用 cons 运算符 (:) 进行尾递归!!!

        处理调用堆栈的一种方法是添加accumulator。 (并不总是可以只添加一个累加器。但大多数 一个处理的递归函数是primitive recursive,因此可以 转换为tail recursive functions。)

        在累加器的帮助下,可以制作这个例子 工作,使用 cons 运算符(:)。 累加器——在我的例子中是ys——累加当前结果并作为参数传递。由于累加器,我们现在能够 使用 cons 运算符通过附加 每次都是我们初始列表的首位。

        reverse' :: (Ord a) => [a] -> [a] -> [a]
        reverse' (x:xs) ys = reverse' xs (x:ys)
        reverse' [] ys     = ys
        

        这里有一点需要注意。

        累加器是一个额外的参数。不知道Haskell 提供默认参数,但在这种情况下会很好, 因为你总是用一个空列表调用这个函数 像这样的累加器:reverse' [1, 2, 3, 4] []

        有很多关于尾递归的文献,我是 肯定有很多类似的问题 StackExchange / StackOverflow。如有错误请指正。

        亲切的问候,

        编辑 1

        Will Ness 为感兴趣的人指出了一些指向非常好的答案的链接:

        编辑 2

        好的。 感谢 dFeuer 和他的更正,我想我了解 Haskell 好一点。

        1.$! 超出我的理解范围。在我所有的测试中,它似乎 让事情变得更糟。

        2.正如 dFeuer 指出的: 代表(:) 应用到xy 的thunk 在语义上与x:y 相同,但占用更多内存。所以这对于 cons 运算符来说是特殊的 (和懒惰的构造函数),没有必要以任何方式强制执行。

        3.如果我使用非常相似的函数来对列表的整数求和, 通过 BangPatterns 或 seq 函数进行严格评估 如果使用得当,将防止堆栈变得太大。例如:

        sumUp' :: (Num a, Ord a) => [a] -> a -> a
        sumUp' (x:xs) !y = reverse' xs (x + y)
        sumUp' [] y      = y
        

        注意 y 前面的爆炸。我在ghci中试过了 占用更少的内存。

        【讨论】:

        • Haskell 从不延迟延迟延迟构造函数的应用(因为这样做从来没有任何优势),因此没有必要也没有优势,手动强制这些结果。您甚至可能会通过强制执行已经评估的内容来减慢速度!
        • @dfeuer 我不太确定 Haskell 的严格性。 wiki.haskell.org/Performance/Accumulating_parameter 虽然确实建议人们应该考虑对累加器使用严格的评估:“我们需要解决一个关于堆栈溢出的小问题。当我们传递列表时,该函数将累积 (1 + acc) thunk。一般来说,使你的累加器参数严格是有意义的,因为它的值将在最后需要。"
        • GHC 一般会延迟函数应用和模式匹配,但不会延迟构造函数应用。原因是表示(:) 应用到xythunk 在语义上与x:y 相同,但占用更多内存。构造函数是特殊的。严格的构造函数并没有那么特别。
        • 但我之前的观点是,如果我通常调用f (g x y),GHC 将创建一个代表g x y 的thunk 并将该thunk 传递给f。但是如果g 是一个惰性构造函数(即一个构造函数的定义不使用!),GHC 将改为直接应用g 并将结果传递给f。语义相同,但速度更快且节省内存。
        • 我喜欢在 Haskell 中指向 ++ 函数的源代码是很自然的,在我看来,你很少会在其他语言中看到这一点,例如我从来没有想过要在说 JavaScript 中查看 concat 的内部定义,也从来没有见过任何人或任何 JS 书籍推荐查看源代码
        【解决方案5】:

        :将一个元素组合到一个列表中。

        ++ 附加两个列表。

        前者有类型

        a -> [a] -> [a]
        

        而后者有类型

        [a] -> [a] -> [a]
        

        【讨论】:

        • 对于 Lisp-vocabulary-challenged,"cons" 构造一个新的列表节点并将其添加到列表的头部。
        猜你喜欢
        • 2013-10-17
        • 2014-06-14
        • 1970-01-01
        • 2021-06-27
        • 1970-01-01
        • 1970-01-01
        • 2011-08-18
        • 2011-08-18
        • 2017-11-27
        相关资源
        最近更新 更多