認識 Lambda/Closure(6)一級函式與 Lambda 演算
認識 Lambda/Closure(5)Java 的稻草人提案 << 前情 值可以指定給變數。一級值(first-class value)可以傳入函式或由函式中傳回。在多數程式語言中,基本型態或物件是一級值,那麼函式或運算式呢(expression)? 在一些語言中,函式是一等公民(first-class citizen)(或者稱為高階函式)。例如,在 認識 Lambda/Closure(1)中我們看過,JavaScript 的函式是物件,也就是說,它們是一級值,可以指定給變數、傳入函式或從函式中傳回。 不過,為什麼一級函式也稱為 Lambda?在回答這個問題之前,我們必須認識一下 Lambda 演算(也可以寫成 λ 演算)。簡單地說,在 λ 演算中,函式是僅帶一個參數的運算式。參數也可以接受帶有一個參數的函式。λ 演算中的函式是匿名的。例如,若將數學函數 λ x. x * 2 如果採用 JDK8 最後採用的語法,則可以寫為: x -> x * 2 也就是說,Lambda 運算式會將 (x -> x * 2)(2) = 2 * 2 = 4 如果有個函數 h(x) = g(f(x)) = f(x) - 1 = x * 2 - 1 使用 Lambda 運算式將上面套用過程重新寫過的話,就會得到新的 Lambda 運算式。 (y -> y - 1)(x -> x * 2) = x -> x * 2 - 1 在 λ 演算中,函式是運算式,也稱為 Lambda 函式,它是僅帶一個參數的函式。那麼,如何表示數學上具備兩個輸入的函數呢? 來想看看 (x, y) -> x * x + y * y = x -> (y -> x * x + y * y) 將 (x -> (y -> x * x + y * y))(a)(b) = (y -> a * a + y * y)(b) = a * a + b * b 在 λ 演算中,任何超過一個參數以上的函式,可以由數個單參數的函式依序套用而得。我們也可以使用 Lambda 演算來實作控制流程函式,像是 let not = * false -> true * true -> false let and = * false value -> false * true value -> value * value false -> false * value true -> value let or = * false value -> value * true value -> true * value false -> value * value true -> true let if = cond -> tv -> fv -> (cond and tv) or (not cond and fv) 上面的 if(true)(a)(b) = ((cond or fv) and (cond and tv))(true)(a)(b) =((true and tv) or (not true and fv))(a)(b) =((true and a) or (not true and fv))(b) =(true and a) or (not true and b) = a or (false and b) = a or false = a 我們也可以實作一個 let unless = cond -> fv -> tv -> (cond or fv) and (cond and tv)
unless(true)(a)(b) = ((cond or fv) and (cond and tv))(true)(a)(b) = ((true or fv) and (true and tv))(a)(b) = ((true or a) and (true and tv))(b) = (true or a) and (true and b) = true and b = b 不同的語言會提供不同的語法來支援 Lambda 運算式。例如,用 JavaScript 來表達 function(x) { return x * 2; }; 這個語法看起來有點囉嗦。基本上,我們關心的只是 我們曾經看過,在 Java 中最接近 Lambda 函式的東西是匿名類別。如果想用它來表達 public interface Func<P, R> { R apply(P p); } 接著,可以使用匿名類別來實作 new Func<Integer, Integer>() { public Integer apply(Integer x) { return x * 2; } }; 哇喔!好大一垞!匿名類別的語法已經夠惱人了,我們甚至還得宣告型態,因為 Java 是靜態語言。如果我們打算進行函式合成的話,像是 public static <A, B, C> Func<A, C> compose(final Func<A, B> f, final Func<B, C> g) { return new Func<A, C>() { public C apply(A x) { return g.apply(f.apply(x)); } }; } 你能一眼看出我們打算做什麼嗎?基本上,我們真正需要的只是 compose( new Func<Integer, Integer>() { public Integer apply(Integer x) { return x + 2; } }, new Func<Integer, Integer>() { public Integer apply(Integer y) { return y * 3; } } ); 正如在認識 Lambda/Closure(5) 中談過的,有關 Java 不需要 Lambda/Closure 的爭論是對,只是相對的代價是,撰寫更多的程式碼。有時候,也許是在多數情況下,我們很難看出程式碼到底想表達什麼,即使只是做個如上 |
changyuheng
04/11
"public C apply(A, x) {" 這一行中,A 和 x 中間多了一個 ","。