Functional programming 函数式编程模型效率(特定于Erlang)

Functional programming 函数式编程模型效率(特定于Erlang),functional-programming,erlang,Functional Programming,Erlang,嗨,我是Erlang世界的新手。当我想到我们需要如何解决下面的问题时(有一长串类似的问题),我认为这是非常低效的,因为我们谈论了很多递归。显然,像C/Java这样的语言不需要笨拙的递归来解决这个问题,但是对于Erlang(我想其他函数式编程语言也需要,也许?),您必须这样做 示例3-追加 此程序连接两个列表: append([], List) -> List; append([First|Rest], List) -> [First | append(Rest,List)].

嗨,我是Erlang世界的新手。当我想到我们需要如何解决下面的问题时(有一长串类似的问题),我认为这是非常低效的,因为我们谈论了很多递归。显然,像C/Java这样的语言不需要笨拙的递归来解决这个问题,但是对于Erlang(我想其他函数式编程语言也需要,也许?),您必须这样做


示例3-追加

此程序连接两个列表:

append([], List) -> List;

append([First|Rest], List) -> [First | append(Rest,List)].
有人能解释一下为什么这不是一个问题吗?

首先,阅读关于递归的文章

至于笨拙之处,不要忘记Erlang列表是单链接列表,因此您只有一个指向列表头部的“指针”,并且需要通过遍历列表来访问元素。 这将需要相同的数量,但不同于那些使用所有指针或引用杂耍的语言

至于效率,您可以以尾部递归方式实现它。尾部递归被优化(),编译代码类似于C++中实现的方式,唯一的区别是,不是代码指针跳跃,堆栈指针是倒带,等等。
<>无论如何,尝试在爪哇和C++中实现非常相同的功能,然后我们会看到哪一个是笨拙的和更可读的。

尾递归在每个函数语言中都是优化的-它与过程语言的循环(或者,AHEM,GOTO;-)相比没有开销。虽然您给出的具体示例可能是也可能不是“尾部递归”,这取决于语言(例如,惰性函数语言与急切函数语言),但当您使用一种特定语言进行编码时,通常可以重构为纯尾部递归表达式。。。如果需要的话

在惰性语言中,例如Haskell,在(Haskell的等价物)
[First | append(Rest,List)]
中的
append将被保存为一个“thunk”,以便在需要时执行,而不是立即进行扩展。然后,当稍后,有人模式匹配头/尾结构的砰砰声时——然后,而且只有在那时,砰砰声才被第一次执行


急切的(非懒惰的)语言有不同的方法,但它的效率并没有降低(尽管懒惰的语言爱好者可能会认为它不够优雅;-)。

那么,为什么你认为递归效率低下呢

在Erlang的例子中,它使用尾部递归(.netIL也可以这样做),这实际上在处理方面稍微高效一些

例如,对于尾部递归,CPU执行如下操作:

var first_list, second_list

label START_ITER:
  if(first_list is empty)
    goto FINISH

  var first_elem = first_list[0]
  var first_list.start_element = first_list[1]

  second_list[n+1] first_elem
  goto START_ITER

label FINISH:
  return second_list
var first_list, second_list

var i = 0
var limit = first_list.length

label START_ITER:
  if(i == limit)
    goto FINISH

  second_list[n+i+1] = first_list[i]
  i += 1
  goto START_ITER

label FINISH:
  // Done
而对于for循环,您可以得到如下结果:

var first_list, second_list

label START_ITER:
  if(first_list is empty)
    goto FINISH

  var first_elem = first_list[0]
  var first_list.start_element = first_list[1]

  second_list[n+1] first_elem
  goto START_ITER

label FINISH:
  return second_list
var first_list, second_list

var i = 0
var limit = first_list.length

label START_ITER:
  if(i == limit)
    goto FINISH

  second_list[n+i+1] = first_list[i]
  i += 1
  goto START_ITER

label FINISH:
  // Done
尾部递归示例需要注意的是,列表拆分只会更改列表中第一个\u列表指向的节点。for循环示例使用更多的变量赋值来完成大致相同的事情

这也说明了尾部递归(只是一个goto)和普通递归之间的区别,后者实际上会将函数加载到堆栈上。这在尾部递归和erlang中通常不会发生

这是一个很接近的调用,但是在for循环中有一些额外的检查和添加,所以我不能推荐一个在效率方面优于另一个,但是在风格方面,递归已经完美地完成了

我认为最好将其表述为“迭代是人类的,递归是人类的,神圣的”

您可能不知道“erlc-S”和“To_core”选项,它们允许您检查字节码(BEAM或HiPE)

嗨,我是C世界的新手。当我想到我们需要如何解决以下问题时(有一长串类似的问题),我认为这是非常低效的,因为我们正在谈论大量的循环。很明显,像Erlang这样的语言不需要笨拙的循环来解决这个问题,但是对于C语言(我想其他过程编程语言也需要这样做,也许吧?),您必须这样做

看到我在那里做了什么吗?;)


正如其他人所说,递归是许多语言中解决问题的一种完全正常(且有效)的方法。没有直接关系,但是。。。对于某些事情来说,递归比循环更容易理解(当然,反过来也是正确的)。。。fib(n)。

笨拙的递归?如果您认为递归很笨拙,那么您可能不应该学习函数式编程。常规编程可能也有点太难了。你问这个问题的方式很有挑衅性。我意识到,
append
函数并不是你的论点的核心(我相信在下面的答案中已经彻底纠正了这一点),但是,在Erlang中,你不会编写这个函数。。。相反,您只需编写
List1++List2
。Erlang没有Haskell的这种简洁特性(但?:)。此外,给出的示例不是tail-recursive。一个限定词是:“每种函数式语言”-Clojure都是函数式的,不进行尾部调用优化-尽管这是因为JVM中缺乏对它的支持(JVM语言开发人员显然一直在游说添加这些内容。)Clojure有一个很好的“重现”但是,表单。Common Lisp、Clojure和Scala都是不需要TCO的函数式语言。与
goto
相比,TCO可能会有性能开销,因为它可能需要重新排序堆栈帧。请注意,TCO是一种空间优化,而不是时间优化。