【问题标题】:How does function evaluation work in HaskellHaskell 中的函数求值是如何工作的
【发布时间】:2018-10-21 02:06:52
【问题描述】:

我觉得我对函数式编程的知识有点欠缺,所以当我看到this 第一页上的说明时,我决定上网看看并遵循教程以变得更好

“假设您有一个不可变的数字列表 xs = [1,2,3,4,5,6,7,8] 和 一个函数 doubleMe 将每个元素乘以 2,然后 返回一个新列表。如果我们想将列表乘以 8 命令式语言并做了 doubleMe(doubleMe(doubleMe(xs))),它会 可能通过列表一次并制作副本然后返回 它。然后它会再通过列表两次并返回 结果。”

根据我对函数式编程的了解,这似乎是错误的,让我告诉你原因:

doubleMe = (\x.* 2 x)

所以

doubleMe doubleMe doubleMe xs

beta 会减少到:

(\x.* 2 x) doubleMe doubleMe xs ->beta
(* 2 doubleMe) doubleMe xs 
(* 2 (\x.* 2 x)) doubleMe xs ->eta
(\x.* 2 * 2 x) doubleMe  xs ->beta
(* 2 * 2 doubleMe)  xs
(* 2 * 2 (\x.* 2 x)) xs ->eta
(\x.* 2 * 2 * 2 x) xs ->beta
(* 2 * 2 * 2 xs) ->beta
(* 4 * 2 xs) -> beta
(* 8 xs)

这意味着该函数是 beta 等价于 (\x. * 8 x)

我的印象是 Haskell 编译器在执行之前执行了这种缩减,这意味着不,它不会像本教程建议的那样对列表进行 3 次传递,它只会进行一次。我错了吗?如果是这样,那为什么 Haskell 不这样做呢?肯定会大大提高性能。

【问题讨论】:

  • 我认为这种优化被称为“融合”,编译器会尝试这样做,但总的来说它很棘手,并且是一个正在进行的研究领域。 stackoverflow.com/q/38905369/14955
  • 请注意,引用的部分说的是命令式语言,所以不是在谈论 Haskell 在那里做了什么。也就是说,LYAH sn-p 很困惑——它所描述的区别与命令式与函数式编程语言无关,而与严格与惰性语言有关。无论如何,您对doubleMe 的定义不是 LYAH 所描述的,因为您的定义具有 Num a => a -> a 类型,但 LYAH 的假设函数具有 Num a => [a] -> [a] 类型。
  • @Alexis King 我明白了,所以它确实做了我认为的减少,它只是没有一直到 * 8,它对每个元素做了 2*2*2*x执行 3 次操作而不是 1 次,并且只执行 1 遍。 (我有点傻,应该多注意下一段)。
  • @Thilo 这就是我感兴趣的,谢谢!
  • 您将doubleMe doubleMe doubleMe xsdoubleMe (doubleMe (doubleMe xs)) 混淆了。

标签: haskell


【解决方案1】:

我认为你只是误读了那段。它说(强调我的):

如果我们想将列表乘以 8在命令式语言中并执行doubleMe(doubleMe(doubleMe(xs))),它可能会通过列表一次并复制然后返回。

命令式语言是像 C 或 Python 这样的语言。不是 Haskell。

同一段继续将该行为与“惰性语言”的行为进行对比:

在一种懒惰的语言中......它只会在列表中传递一个,并且只有在你真正需要它的时候。这样,当您想要从惰性语言中获得某些东西时,您只需获取一些初始数据并有效地对其进行转换和修改,使其最终与您想要的相似。

这更接近您的预期。

【讨论】:

  • 是的,我想我也这样做了,但它确实让我想知道是否有办法让 haskell 将函数一直减少到 (\x.* 8 x) 而不是 (\ x. * 2 * 2 * 2 x) - 每个元素三个操作。
  • 这种优化实际上对编译器来说应该很容易做到。
猜你喜欢
  • 2017-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-08
  • 2012-04-23
  • 1970-01-01
相关资源
最近更新 更多