【问题标题】:Learning Haskell maps, folds, loops and recursion学习 Haskell 映射、折叠、循环和递归
【发布时间】:2011-03-03 07:37:08
【问题描述】:

我刚刚涉足 Haskell 世界,作为我的编程启蒙之旅的一部分(从程序性到 OOP 再到并发到现在的功能性)。

我一直在尝试在线Haskell Evaluator

但是我现在遇到了一个问题:

创建一个给出数字数组总和的简单函数。

在程序语言中,这对我来说很容易(使用递归)(c#):

private int sum(ArrayList x, int i)
{
  if (!(x.Count < i + 1)) {
        int t = 0;

        t = x.Item(i);
        t = sum(x, i + 1) + t;
        return t;
    }
}

一切都很好,但是我在 Haskell 的失败尝试是这样的:

let sum x = x+sum  in map sum [1..10]

这导致了以下错误(来自上述网站):

Occurs check: cannot construct the infinite type: a = a -> t

请记住,我在过去 30 分钟内只使用了 Haskell!

我不是在寻找一个简单的答案,而是对它的更多解释。

【问题讨论】:

    标签: haskell recursion types functional-programming


    【解决方案1】:

    让我们看一下这个的第一部分:

    let sum x = x+sum 
    

    在这种情况下,总和的类型是什么?它接受一个数字并返回一个接受数字的函数,该数字返回一个接受数字等的函数,如果你已经编写了它 让总和 x = + x 你会有一个函数,它接受一个数字并返回函数 +x。 和 让总和 = + 将返回一个接受两个整数并将它们相加的函数。

    现在让我们看看第二部分。 在地图总和 [1..10] map 接受一个参数的函数并将其应用于列表的每个元素。那里没有插入累加器的空间,所以让我们看看其他列表函数,特别是 foldl、foldr。这两个都采用两个参数的函数,一个列表和一个起始值。 foldl 和 foldr 之间的区别在于它们开始的那一侧。 l 左为 1 + 2 + 3 等,r 右为 10 + 9 + 8 等。

    让 sum = (+) in foldl sum 0 [1..10]

    【讨论】:

      【解决方案2】:

      你需要阅读一个好的教程,有很多大的误解。

      首先,我假设您的意思是列表而不是数组。数组存在于 Haskell 中,但它们不是您在初学者级别会遇到的东西。 (更不用说您正在使用 [1..10] ,它是数字 1 到 10 的列表)。

      你想要的函数实际上是内置的,叫做 sum,所以我们必须调用我们的其他东西,new_sum:

      new_sum [] = 0
      new_sum (h:t) = h + (sum t)
      

      【讨论】:

      • 谢谢,没有尝试代码并检查我的类型(坏我)。固定。
      • 这是我难以理解的 (sum t) 部分,但在 Norman 的回答和进一步阅读之后,现在是有道理的。
      【解决方案3】:

      我不是在寻找一个简单的答案,而是对它的更多解释。

      在 = 的左侧,您使用 sum 作为应用于 x 的函数。编译器不知道x 的类型,所以编译器使用类型变量a 来代表“x 的类型”。此时编译器也不知道函数sum 的结果类型,所以它选择另一个类型变量,这个类型t,来代表结果类型。现在在左侧,编译器认为x 的类型是a -&gt; t(函数接受a 并返回t)。

      在 = 的右侧添加 xsum。在 Haskell 中可以添加所有类型的数字,但是只有当它们具有 same 类型时才能添加两个数字。所以这里编译器假定sumx的类型相同,即类型a

      但在 Haskell 中,标识符只有一种类型——可能是一种非常复杂的类型,但仍然是一种类型。这包括sum,它的类型在`符号两边应该是相同的,所以编译器会尝试解方程

      a = a -> t
      

      at 没有值可以解决这个等式。根本无法解决。没有a 使得a 等于接受自身作为参数的函数。因此出现错误消息

      cannot construct the infinite type: a = a -> t
      

      即使有所有的解释,这也不是一个很好的错误信息,是吗?

      欢迎来到 Haskell :-)


      附:您可能会喜欢尝试“Helium, for learning Haskell”,它为初学者提供了更好的错误消息。

      【讨论】:

      • 这是我正在寻找的全面解释。阅读本文并进一步阅读后,我现在理解得更好了。
      【解决方案4】:

      'sum' 获取一个值列表并将其缩减为单个值。您可以将其写为显式循环(请记住,Haskell 没有循环关键字,但使用递归)。请注意定义如何根据列表的形状分为两部分:

      mysum []     = 0
      mysum (x:xs) = x + mysum xs
      

      或者更高效,采用tail-recursive 风格:

      mysum xs = go 0 xs
         where
            go n []     = n
            go n (x:xs) = go (n+x) xs
      

      但是,Haskell 有一个丰富的控制结构库,可以在惰性列表上运行。在这种情况下,可以使用 reduce 函数将列表减少到单个值:折叠。

      所以mysum可以写成:

      mysum xs  = foldr (+) 0 xs
      

      例如:

      Prelude> foldr (+) 0 [1..10]
      55
      

      您的错误是使用 map,它一次转换一个列表,而不是 fold

      我建议您先介绍 Haskell,也许是“Programming in Haskell”,以了解函数式编程的核心概念。其他不错的介绍材料在描述in this question.

      【讨论】:

      • 我不知道0播放的意义是什么,请您详细介绍一下零的使用吗?
      • 0 是折叠中累加参数的初始值。它是原始来源中的 't' 值。
      猜你喜欢
      • 1970-01-01
      • 2023-04-09
      • 2019-05-20
      • 1970-01-01
      • 2014-02-13
      • 2020-04-04
      • 1970-01-01
      • 2011-07-12
      • 1970-01-01
      相关资源
      最近更新 更多