計算機顧名思義,執(zhí)行計算功能彬呻。然而通常計算機是通過重復計算得出結(jié)果衣陶。例如計算從1到100的求和柄瑰,就是一個個數(shù)進行累加。這樣的重復性工作剪况,人類顯然不太擅長教沾,所以人發(fā)明了算法。高斯小朋友就實現(xiàn)了一個等差數(shù)列的求和算法译断。
既然計算機通過快速的重復計算得出結(jié)果详囤。那么對數(shù)據(jù)源進行循環(huán)處理就是一種基本的控制結(jié)構(gòu)。經(jīng)典的循環(huán)控制都是通過while镐作,for語句實現(xiàn)藏姐。很可惜,elixr中并沒有這樣的語句该贾。不過不用擔心羔杨,elixir提供了優(yōu)雅而強大的遞歸來進行循環(huán)迭代處理。
遞歸
在函數(shù)內(nèi)調(diào)用函數(shù)自身即為遞歸杨蛋。正常情況下兜材,打印一些一些自然數(shù)可以這么寫:
# python
In [1]: for i in range(1, 6):
...: print i
...:
1
2
3
4
5
In [4]: n = 1
In [5]: while n <= 5:
...: print n
...: n += 1
...:
1
2
3
4
5
Elixir 中使用遞歸則為:
defmodule Nums do
def print(1), do: IO.puts 1
def print(n) do
print(n - 1)
IO.puts n
end
end
iex(1)> Nums.print(5)
1
2
3
4
5
:ok
列表遞歸
Lisp擁有很高的聲譽,它本身就是指列表進行計算逞力。前面我們提及曙寡,List的實現(xiàn)是鏈表結(jié)構(gòu),因此讀取列表第一個元素將會很快寇荧。elixir還提供了|
操作符举庶。下面就使用列表來完成加和計算:
defmodule Math do
def sum([]) do
0
end
def sum([head|tail]) do
head + sum(tail)
end
end
IO.puts Math.sum([1, 2, 3, 4, 5])
尾遞歸
使用遞歸來做循環(huán)控制是函數(shù)式語言的一大特點。別的語言通常在處理遞歸的時候存在著一個最大遞歸深度揩抡。而elixir對遞歸在編譯上做了優(yōu)化户侥。當然并不是針對所有遞歸,而是專門指尾遞歸(tail recursion)峦嗤。至于尾遞歸和遞歸的差別蕊唐,可以閱讀遞歸和尾遞歸。其形式大概就是自己調(diào)用自己的時候烁设,并不是作為表達式替梨,大概形式如下:
def original_fun(...) do
...
another_fun(...) # Tail call
end
上述加和操作用尾遞歸重寫如下:
defmodule Math do
def sum(list) do
_sum(list, 0)
end
defp _sum([], accumulator) do
accumulator
end
defp _sum([head|tail], accumulator) do
_sum(tail, head + accumulator)
end
end
IO.puts Math.sum([1, 2, 3, 4, 5])
更多遞歸
上述遞歸的控制基于函數(shù)參數(shù)的模式匹配,關于條件的控制装黑,還有case和cond宏副瀑,下面結(jié)合這些控制結(jié)構(gòu)來重寫上述的求和計算。
Lisp中常用cond結(jié)構(gòu)曹体,下面情況cond的結(jié)果:
defmodule Math do
def sum(list) do
_sum(list, 0)
end
def _sum(list, accumulator) do
cond do
list == [] ->
accumulator
[head|tail] = list ->
_sum(tail, head+accumulator)
end
end
end
IO.puts Math.sum([1, 2, 3, 4, 5])
再看case的結(jié)果:
defmodule Math do
def sum(list) do
_sum(list, 0)
end
def _sum(list, accumulator) do
case list do
[] -> accumulator
[head|tail] -> _sum(tail, head + accumulator)
end
end
end
IO.puts Math.sum([1, 2, 3, 4, 5])
由此可見俗扇,使用遞歸實現(xiàn)循環(huán)控制,遇到條件判斷的時候箕别,即可以使用函數(shù)參數(shù)的模式匹配铜幽,也可以使用cond和case方式進行判斷滞谢。其中cond和case內(nèi)部邏輯分支也是基于模式匹配。
列表解析
todo
總而言之除抛,使用elixir的遞歸實現(xiàn)迭代功能狮杨,盡量使用尾遞歸。尾遞歸經(jīng)過編譯器優(yōu)化到忽,不會因為調(diào)用棧出現(xiàn)問題橄教,也不會引起額外的內(nèi)存開銷。普通遞歸則會出現(xiàn)最大遞歸棧的問題喘漏。實際上护蝶,函數(shù)式語言的特點就是函數(shù)。在實現(xiàn)循環(huán)的功能翩迈,Elixir提供了一些高階函數(shù)用來處理持灰,通過這些高階函數(shù)的抽象,隱藏了遞歸調(diào)用的細節(jié)负饲。開發(fā)者掌握這些高階函數(shù)堤魁,更有利于實現(xiàn)功能需求。Enum和Stream提供了大部分所需要的函數(shù)返十,下一節(jié)繼續(xù)探索高階函數(shù)妥泉。