用純函數式思惟在Java8下寫的一段奇葩程序

首先說一下什麼是純函數式。在個人理解,「純函數式」用一句話就能夠描述:Anything is value.——個人理解不必定準確,但我就是這麼理解的。java

就是全部的東西都是值——沒有變量;包括函數在內都是值——是值,就能夠傳遞(包括函數)。算法

 

爲何說這段程序是奇葩呢?編程

其1、傳統的Java是面向對象的,自從Java8中加入了lambda,Java就變成了「面向對象」和「函數式」兩種方式的混合語言。這段程序所有使用lambda的語法來寫,與日常寫的Java風格徹底不一樣。數據結構

其2、在Java的對象中保存數據一般是用對象的屬性,lambda表達式本質上仍然是對象,但它並無屬性,但咱們卻成功的在lambda中保存了數據,這相對於傳統的Java編程思惟也是一種跳躍。app

其3、在Scheme中實現一樣的函數(或lambda)很是簡潔,也很容易讀,而在Java中的實現,可讀性好差,以至於我本身都快看不懂了,因此說是「奇葩」。 函數

 

這段程序用兩種方式實現了一樣的功能:翻譯

1.實現一個函數cons,這個函數有兩個參數x和y,並返回一個東西(這個東西如下簡稱爲c)。對象

2.實現一個函數car,傳入c,並返回原來傳入cons中的x。編譯器

3.實現一個函數cdr,傳入c,並返回原來傳入cons中的y。io

這其實是Scheme中自帶的「序偶」,不過即便Scheme語言自己的庫不自帶cons,咱們本身實現也是很簡單的(下面的程序中,我在註釋部分列出了Scheme的實現);Java骨子裏是面向對象的基因,若是用面向對象的方式來實現上述功能是很是簡單的,但用lambda的語法來實現就顯得奇葩了。

 

下面先把奇葩貼出來,而後在後面的註釋中解釋一下:


import java.util.function.BiFunction;
import java.util.function.Function;

public class TestCons {

    public static void main(String[] args) {
        testCons1();
        testCons2();
    }

    private static void testCons1() {
        /*
        (define (cons x y)
          (lambda (m)
            (cond ((= m 0) x)
                  (else y))))
        (define (car z) (z 0))
        (define (cdr z) (z 1))
        上面幾行Scheme代碼翻譯成Java是以下三行代碼
         */
        BiFunction<Object, Object, Function<Integer, Object>> cons = (x, y) -> m -> m == 0 ? x : y; // 註釋1
        Function<Function, Object> car = z -> z.apply(0); // 註釋2
        Function<Function, Object> cdr = z -> z.apply(1); // 註釋3

        Function c = cons.apply(3, "abc"); // 調用cons,並傳入兩個值,建立了對象c
        System.out.println(car.apply(c)); // 從c中取出第一個值
        System.out.println(cdr.apply(c)); // 從c中取出第二個值
    }

    private static void testCons2() {
        /*
        (define (cons x y)
          (lambda (m) (m x y)))
        (define (car z)
          (z (lambda (p q) p)))
        (define (cdr z)
          (z (lambda (p q) q)))
        上面幾行Scheme代碼翻譯成Java是以下三行代碼
         */
        BiFunction<Object, Object, Function<BiFunction, Object>> cons = (x, y) -> f -> f.apply(x, y); // 註釋4
        Function<Function<BiFunction, Object>, Object> car = f -> f.apply((a, b) -> a); // 註釋5
        Function<Function<BiFunction, Object>, Object> cdr = f -> f.apply((a, b) -> b); // 註釋6

        Function c = cons.apply(3, "abc"); // 調用cons,並傳入兩個值,建立了對象c
        System.out.println(car.apply(c)); // 從c中取出第一個值
        System.out.println(cdr.apply(c)); // 從c中取出第二個值
    }
}


註釋1:此行建立一個叫cons的lambda表達式,此表達式有兩個參數x和y,並返回另一個lambda,這個lambda有一個整數類型的參數m,且當m爲0時,返回x,不然返回y。

註釋2:此行建立一個叫car的lambda,此lambda有一個參數,且這個參數也是一個lambda(z),car的lambda體中是把0傳入z中,並獲得返回值。

結合「註釋1」和「註釋2」這兩行,咱們能夠這樣解釋:cons返回的lambda能夠作爲car的參數。

註釋3:和「註釋2」差很少,再也不綴述。

註釋4:此行建立一個叫cons的lambda,此lambda有兩個參數x和y,反返回另一個lambda,這個lambda有一個參數,且這個參數也是一個lambda(f),在cons返回值的lambda體中應用f,並把cons的兩個參數作爲f的兩個參數——至關拗口——簡單點說就是cons並不作什麼,只是把x和y,交給一個lambda,而這個lambda也不作什麼,只是等着另一個lambda(f)來處理x和y,而這個f要經過參數傳過來。

註釋5:此行建立一個小car的lambda,此lambda有一個參數(此參數可傳入cons返回的lambda),從「註釋4」中咱們知道cons返回的lambda還須要一個lambda作爲參數來處理兩個參數,因此咱們傳入一個(a, b) –> a,這裏在a和b中返回前者,這就是car的目的。

註釋6:和「註釋5」差很少,再也不綴述。

 

處處都是lambda,很難讀,但在Scheme中徹底同樣的算法實現就很簡潔,可讀性很好,這是爲何呢?

我認爲這是S表達式的語法結構造成的效果——S表達式是以數據結構的方式存儲程序的,這樣的狀況下,假設Scheme中沒有lambda,此時咱們要擴展編譯器來支持lambda,則咱們不須要修改編譯器的parser部分——但Java的lambda沒有辦法與現有的其它語法的結構同樣,因此就只能新增新的語法結構了,但又要與原有的基因融合,這樣雖然lambda在本質上仍然是對象,但在表現形式上與原有的Java卻有很大的排異反應。

這不是一兩句話能說得明白的,也有點扯遠了。

 

下面再演示一個邱奇計數的例子,這個就不寫註釋了:


import java.util.function.Function;

public class testChurchNum {
    public static void main(String[] args) {
        Function<Function<Function<Object, Object>, Function>, Function<Function<Object, Object>, Function>>
                add_1 = n -> f -> x -> f.apply(n.apply(f).apply(x));

        Function<Function<Object, Object>, Function> zero = f -> x -> x;
        Function<Function<Object, Object>, Function> one = add_1.apply(zero);
        Function<Function<Object, Object>, Function> tow = add_1.apply(one);
        Function<Function<Object, Object>, Function> one_1 = f -> x -> f.apply(x);
        Function<Function<Object, Object>, Function> tow_1 = f -> x -> f.apply(f.apply(x));

        Function f = x -> (((Integer) x) + 1);        System.out.println(zero.apply(f).apply(0));        System.out.println(one.apply(f).apply(0));        System.out.println(one_1.apply(f).apply(0));        System.out.println(tow.apply(f).apply(0));        System.out.println(tow_1.apply(f).apply(0));    }}

相關文章
相關標籤/搜索