遞迴與Smalltalk式介詞應用
前陣子用 Ruby 寫一個文件區塊的格式化工具,它會先載入整個文件區塊到陣列,然後依序將每個段落交給不同的方法處理。 被調用的方法,會從陣列中取出它要處理的段落,直到段落結束。 因為處理過的段落內容已經從陣列中取出,所以下一個方法總是從陣列的開頭繼續處理。
我先用 Ruby 寫幾個小程式來演練概念,寫著寫著就玩起來了。 一共寫了三種作法,分別應用了不同的 Ruby 語法。
- 平行指派,後綴式迴圈、無遞迴
- 遞迴式
- 遞迴式,Smalltalk 式介詞應用,加上迭代器
平行指派,後綴式迴圈、無遞迴
a = [1, 2, 3]
v = []
def take x
#yield
return x.shift, x
end
i = -1 #這是一個小瑕玼
v[i+=1], a = take a while a.size > 0
#上行讀作: 當 a 還有東西時,就拿出一個放到 v 中。
p a
p v
i = -1
a[i+=1], v = take v until v.size < 1
#上行讀作: 從 v 拿出一個東西放到 a 中,直到 v 裡面沒有東西。
p a
p v
沒有用到遞迴,記憶體需求比遞迴式少。 不過遞迴式的寫法也不難,一併完成吧。
遞迴式
a = [1, 2, 3]
v = []
#take([H|T]) -> H, take(T);
#take([]) -> [].
def take x, y
if x.size > 0
y << x.shift
take x, y
end
return x
end
take a, v
p a
p v
Smalltalk 式介詞應用,加上迭代器
編程大師總是說 Smalltalk 的表達力非常優秀。而 Ruby 的教科書上也常常說 Ruby 的語法可以提供像 Smalltalk 的表達能力,適合開發領域特定語言(DSL)。 那我就用 Ruby 來玩一下類似 Smalltalk 在訊息中使用介詞的表達能力吧。
# take(V, [H|T]) -> take(V, T);
# take(_, []) -> [].
a = [1, 2, 3]
v = []
def take x, preposition, y, &p
if preposition == :from
take y, :to, x, &p
else
if x.size > 0
y << x.shift
yield y.last if block_given?
take x, preposition, y, &p
end
end
return x
end
take a, :to, v
#英文是我們的第一外國語言,應該不用解釋上面那一句話了吧?
# a -> v
p a
p v
#介詞變成 from ,所以方向反過來了。
# a <- v
take a, :from, v
p a
p v
puts '印出每次取得的東西'
take( a, :to, v ) {|n|
puts "Take #{n}"
}
p a
p v
Smalltalk 可寫成:
take a to: v.
take a from: v.
take: x to: y
"your code"
^x
take: x from: y
"your code"
^y
文如其名,近乎日常用語的抽象表達力。 而且 Smalltalk 可以分成兩個訊息(方法)來實作, 不像上頭的 Ruby 用 if 分開流程。程式結構更清晰。
此外,當我在寫遞迴時,我腦中不由得想到 Erlang 的作法。每次從清單中取出頭部處理,尾部作為另一個清單交給下一個方法處理。
method([H|T]) -> H, method(T);
method([]) -> [].
在我學 Erlang 之前,遞迴程式的設計藍圖從未如此清晰地浮現在我眼前。儘管我對 Erlang 的認識還很粗淺,但是它的序列編程風格,已經成功影嚮我改進遞迴設計。
我學過的程式語言不算多,屈指可數。在這之中, Java 語言對我的影嚮最低,趨近於零;事實上我總是儘可能地忘掉 Java 語言的風格,理由不言可知。
每一種程式語言都有許多獨特的語法,構成形形色色的表達力。儘管我們認為其中一些語法只不過是甜頭、糖衣。但語言的表達能力不可思議地影嚮我們的軟體開發生產力。表達能力愈是接近日常口語或日常符號系統的,它的生產力就愈高。
雖然 Smalltalk 等語言並不流行,而且我寫著不同的語言,但那並不妨礙我學習他們的優點。儘可能地嘗試引用他們的概念,強化程式碼的表達能力,提昇生產力。再者,程式碼即文件
,程式的表達能力愈貼近日常用語,我就愈不需要維護多份設計文件。
樂多舊回應