当前位置: 首页 > 知识库问答 >
问题:

只有一个参数函数有效吗?哈斯克尔

红朝
2023-03-14

我已经开始学习Haskell,我读到Haskell中的每一个函数只需要一个参数,我不明白在Haskell的庇护下发生了什么魔法,这使得它成为可能,我想知道它是否有效。

>:t (+)
(+) :: Num a => a -> a -> a

上面的签名意味着()函数接受一个Num,然后返回另一个函数,该函数接受一个Num,并返回一个Num

示例1相对简单,但我开始想知道当函数稍微复杂一点时会发生什么。

在这个例子中,我编写了一个zipWith函数,并以两种方式执行它,一次传递一个参数,一次传递所有参数。

zipwithCustom f (x:xs) (y:ys) = f x y : zipwithCustom f xs ys
zipwithCustom _ _ _ = []
zipWithAdd = zipwithCustom (+)
zipWithAddTo123 = zipWithAdd [1,2,3]

test1 = zipWithAddTo123 [1,1,1]
test2 = zipwithCustom (+) [1,2,3] [1,1,1]

>test1
[2,3,4]
>test2
[2,3,4]
  1. 一次传递一个参数(场景_1)和一次传递所有参数(场景_2)一样有效吗

我意识到,我在一篇帖子中问了很多问题,但我相信这些问题是相互关联的,它们将帮助我(以及其他对Haskell不熟悉的人)更好地理解Haskell到底发生了什么,这使这两种情况都成为可能。

共有1个答案

乜烨霖
2023-03-14

您询问“Haskell”,但Haskell语言规范并不关心这些细节。由实现来选择如何进行评估——规范只说明评估结果应该是什么,并谨慎避免给出必须用于计算该结果的算法。所以在这个回答中,我将谈论GHC,实际上,它是唯一现存的实现。

对于(3)和(4),答案很简单:迭代模式是完全相同的,无论您是一次应用zipBackCustom到一个参数还是一次应用所有参数。(迭代模式是同时迭代两个列表。)

不幸的是,(1)和(2)的答案很复杂。

起点是以下简单的算法:

  1. 将函数应用于参数时,将创建(分配并初始化)闭包。闭包是内存中的数据结构,包含指向函数的指针和指向参数的指针。在执行函数体时,每当提到它的参数时,都会在闭包中查找该参数的值
  2. 就这样

然而,这种算法有点糟糕。这意味着,如果你有一个7参数函数,你可以分配7个数据结构,当你使用一个参数时,你可能需要遵循一个7长的指针链才能找到它。总的所以GHC做了一些稍微聪明一点的事情。它以一种特殊的方式使用程序的语法:如果将一个函数应用于多个参数,它只为该应用程序生成一个闭包,包含的字段数量与参数数量相同。

(嗯...那可能不太正确。实际上,它跟踪每个函数的特性——再次以语法方式定义为定义该函数时使用的=< /code>符号左侧的参数数。如果你将一个函数应用到比它的特性更多的参数,你可能会得到多个闭包或其他东西,我不确定。)

这很好,从这一点来看,你可能会认为你的test1会比test2分配一个额外的闭包。你是对的。。。当优化器未打开时。

但GHC也做了很多优化工作,其中之一就是注意“小”定义并将其内联。几乎可以肯定的是,启用优化后,您的zipWithAddzipWithAddTo123都将被内联到使用它们的任何地方,我们将回到只分配一个闭包的情况。

希望这个解释能让你自己回答问题(1)和(2),但万一不能,这里有一些明确的答案:

  1. 一次传递一个参数和一次传递所有参数一样有效吗?

也许吧。一次传递一个参数可能会通过内联转换为一次传递所有参数,然后它们当然会相同。在没有这种优化的情况下,与一次传递所有参数相比,一次传递一个参数会有(非常轻微的)性能损失。

test1test2几乎肯定会被编译成相同的代码——甚至可能只编译其中一个,而另一个是它的别名。

如果你想阅读更多关于实现中的想法,那么无脊椎无标记G-machine论文比它的标题所暗示的更加平易近人,只是有点过时了。

 类似资料:
  • 我对Haskell比较陌生,我正在努力找到一种实现Haskell的span函数的方法。然而,我的问题比这更一般,因为我不知道如何使函数返回包含所需元素的列表或元组列表。我对列表的问题,例如: 是我不能让函数在列表列表中的第一个列表中添加一个元素。我只知道如何将另一个列表附加到列表列表中。 简而言之,如果您向我解释如何实现span函数,我希望这一切都会清楚。

  • Sonarqube只允许函数的7个参数。我有一个函数,它用8个参数组成字符串。 使此函数只有7个参数。我通过删除最后一个参数<code>dat</code>来拆分函数,但为了计算<code>dat</code>变量,我还需要<code>b</code>和<code>d</code>变量值。因此,在函数返回后,我需要<code>b</code>和<code>d</code>的值来计算<code>da

  • :101:22:ERROR:•在表达式“count words”的第一个参数中的“hello”中,即表达式:countWords[“hello”,“hello”,“world”]中的“[”hello“,”hello“,”world“]”中,无法将预期类型“Char”与实际类型“[Char]”匹配• :101:31:error:•在表达式“count words”的第一个参数中的“world”中,即

  • 我有一个(相当复杂的)数据类型: 现在我发现自己需要另一个数据类型…有两个构造函数。一个与的相同;另一个只存储一个。我有什么选择? 虽然这会起作用,但它也允许类似这样的东西,这是没有意义的。

  • 大家好,我有这样的问题: 它说: 隐式类必须有一个主构造函数,并且在def traverseFilteringErrors的第一个参数列表中只有一个参数 和 类型不匹配。必填:Future[B] = 我是新来的scala,所以我应该怎么做来解决这个问题?

  • 目前我在Solr 4.1和Lucene 4.1的结合中遇到了以下问题。 我有这样一个Solr Bean: 现在我想通过HttpSolrServer将bean添加到Solr数据库中。addBean(bean)和我得到以下例外: 但我不明白为什么。我多次为每个setter提供一个以上参数的类,但它没有问题(它甚至是由Eclipse生成的)。 现在的问题是: 我错过了什么?这可能是什么原因?执行架构中缺