我同意帕维尔的观点,直觉就是主观的。因为我(慢慢地)开始了解 Haskell,所以我可以说出 Clojure 代码的作用,即使我一生中从未编写过一行 Clojure。所以我认为 Clojure 系列相当直观,因为我以前见过它并且我正在适应更实用的思维方式。
让我们考虑一下数学定义,好吗?
{ 0 if x = 0 }
F(x) = { 1 if x = 1 }
{ F(x - 1) + F(x - 2) if x > 1 }
从格式上看,这并不理想——这三个括号排列起来应该是一个巨大的括号——但谁在数呢?对于大多数具有数学背景的人来说,这是斐波那契数列的一个非常清晰的定义。让我们看看 Haskell 中的同样的事情,因为我比 Clojure 更了解它:
fib 0 = 0
fib 1 = 1
fib n = fibs (n - 1) + fibs (n - 2)
这是一个函数,fib
,返回第 n 个斐波那契数。不完全是我们在 Python 或 Clojure 中所拥有的,所以让我们解决这个问题:
fibs = map fib [0..]
这使得fibs
斐波那契数列的无限列表。fibs !! 1
is 1, fibs !! 2
is 1, fibs !! 10
是 55,依此类推。然而,即使在像 Haskell 这样依赖于高度优化的递归的语言中,这也可能是相当低效的。让我们看看 Haskell 中的 Clojure 定义:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
前几个字符非常简单:0 : 1 :
创建一个包含元素 0 和 1 以及更多元素的列表。但剩下的是什么?出色地,fibs
是我们已经得到的列表,并且tail fibs
称为tail
到目前为止我们列表中的函数,它返回从第二个元素开始的列表(有点像Python中的说法fibs[1:]
)。所以我们拿这两个列表 -fibs
and tail fibs
- 我们将它们用拉链拉在一起+
函数(运算符) - 也就是说,我们将每个的匹配元素相加。我们看看吧:
fibs = 0 : 1 : ...
tail fibs = 1 : ...
zip result = 1 : ...
所以我们的下一个元素是 1!但随后我们将其添加回我们的fibs
列出来,看看我们得到了什么:
fibs = 0 : 1 : 1 : ...
tail fibs = 1 : 1 : ...
zip result = 1 : 2 : ...
我们这里有一个递归列表定义。当我们在末尾添加更多元素时fibs
跟我们zipWith (+) fibs (tail fibs)
位,添加元素时我们可以使用更多元素。请注意,Haskell 默认情况下是惰性的,因此仅创建一个像这样的无限列表不会导致任何崩溃(只是不要尝试打印它)。
因此,虽然这在理论上可能与我们之前的数学定义相同,但它将结果保存在我们的fibs
列表(某种自动记忆),我们很少遇到在简单的解决方案中可能遇到的问题。为了完整起见,让我们定义我们的fib
就我们的新功能而言fibs
list:
fib n = fibs !! n
如果我还没有失去你,那很好,因为这意味着你了解 Clojure 代码。看:
(def fib-seq (lazy-cat [0 1]
(map + fib-seq (rest fib-seq))))
我们列出一个清单,fib-seq
。它从两个元素开始,[0 1]
,就像我们的 Haskell 示例一样。我们对这两个初始元素进行惰性连接(map + fib-seq (rest fib-seq))
- 假设rest
做同样的事情tail
在 Haskell 中,我们只是将列表与自身以较低的偏移量组合起来,然后将这两个列表与+
运算符/函数。
在头脑中思考了几次并探索了一些其他示例之后,这种生成斐波那契数列的方法至少变得半直观了。它至少足够直观,让我能够以我不懂的语言发现它。