【小编】Clojure:Lisp的现代方言,用于函数式编程(二)
继续之前的文章,我们接下来讨论Clojure中与函数式编程相关的概念和特性。 ##5.函数式编程概念 ###5.1纯函数 纯函数是没有副作用的函数。它只依赖于输入参数,并且只能通过返回值来影响外部环境。纯函数常常与不可变数据一起工作。例如: ```clojure (defn mystery [input] (if input data-1 data-2)) ``` 如果`data-1`和`data-2`是可变的,那么它们可能被外界改变,对相同的`input`参数,可能输出不同,这与纯函数的特性不符。 ###5.2持久化数据结构 所有Clojure数据结构都是持久的,即Clojure数据结构保持原版数据的同时最大程度与新版本的数据共享结构。例如: ```clojure (def a ’(12)) (def b (cons0 a)) ``` 此时,列表`a`没有改变,列表`b`由元素`0`和`a`的元素组成,并没有再重复构建元素`1`和`2`,`b`直接共享`a`的元素。 ###5.3延迟(Laziness)和递归(Recursion) 函数编程大量使用递归和延迟。 递归:一个函数直接或间接地调用自己。 延迟:一个表达式的值直到真正使用时才计算。计算延迟表达式的值称为实现表达式。 在Clojure中,函数和表达式不是延迟的,但序列一般是延迟的。 ###5.4引用透明(Referential Transparency) 延迟依赖一种特性,这种特性是在任何时候都可以直接将函数调用替换为函数返回值。拥有这种特性的函数被认为是引用透明的,因为注意的函数调用可以被替换但不影响程序的行为。 引用透明受益于: 1.缓存,自动缓存函数结果 2.自动转换复杂表达式 ##6.案例分析:Fibonacci序列 Fibonacci序列是一个经典的递归问题。下面我们将展示如何在Clojure中使用递归和延迟解决这个问题。 ###6.1简单的递归 ```clojure (defn stack-consuming-fibo [n] (cond (n0)0 (n1)1 :else (stack-consuming-fibo (- n1)) )) ``` 这个递归版本会发生栈溢出,因为每次递归都会创建新的函数栈。 ###6.2尾递归(Tail Recursion) 尾递归是递归发生在函数返回的表达式中。 ```clojure (defn tail-fibo [n] (letfn [(fib [current next n]) (if (zero? n) current (fib next (current next) (dec n)))] (fib0N1N n))) ``` 尽管这是尾递归,但由于JVM不能执行尾递归优化(TCO),依然会发生栈溢出。 ###6.3 使用recur进行自我递归 ```clojure (defn tail-fibo [n] (letfn [(fib [current next n]) (if (zero? n) current (recur next (current next) (dec n)))] (fib0N1N n))) ``` 使用`recur`可以得到优化,但仍然会发生栈溢出。 ###6.4惰性序列 ```clojure (defn lazy-seq-fibo ([a b] (let [n (a b)] (lazy-seq-fibo0N1N)) [n] (lazy-seq-fibo (cons0 n)))) ``` 惰性序列是延迟的一种应用,可以在需要时才计算 Fibonacci数列的值,从而避免不必要的计算。 (编辑:衢州站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |