使用 Elixir 推導 Y 組合子

如何遞歸調用匿名函數,這個問題困擾我好久了。直到我據說了 Y 組合子。函數

普通的遞歸函數是這樣的:code

defmodule M do
    def foo(x) do
        case x do
            0 -> 0
            n -> foo(n-1) + n
        end
    end
end

而後我一步步把它改形成匿名函數,首先,函數體大體不會變:遞歸

foo = fn x ->
    case x do
        0 -> 0
        n -> foo.(n-1) + n
    end
end

這裏第二個 foo 的地方應該是 foo 這個函數自己被遞歸調用,然而這個時候 foo 的定義尚未完成。不要緊,遇到不知道的東西,就把它做爲參數吧。class

因此咱們修改了函數的定義,讓它首先從參數 g 接受它自己的定義,因爲 g 就是 bar, 爲了獲得上面的本來的 foo , 須要將它自己做爲參數傳遞給本身,用 g.(g) 來獲得 foo。 這裏有點繞,可能須要多看幾遍。匿名函數

bar = fn g ->
    # 上一步裏的 foo 從這裏開始
    fn x ->
        case x do
            0 -> 0
            n -> g.(g).(n-1) + n
        end
    end
end

baz = bar.(bar)

接下來想辦法先把 g.(g)這個東西替換掉,替換的原則是不改變運行時的執行邏輯:module

bar = fn g ->
    h = fn x -> g.(g).(x) end

    fn f ->
        fn x ->
            case x do
                0 -> 0
                n -> f.(n-1) + n
            end
        end
    end.(h)
end

baz = bar.(bar)

如今能夠把和 foo 有關的邏輯剝離出來了:elixir

foo = fn f ->
    fn x ->
        case x do
            0 -> 0
            n -> f.(n-1) + n
        end
    end
end

bar = fn i ->
    fn g ->
        h = fn x -> g.(g).(x) end
    
        i.(h)
    end
end.(foo)

baz = bar.(bar)

最後將 barbaz 結合起來:co

baz = fn x -> x.(x) end.((fn i -> fn g -> i.(fn x -> g.(g).(x) end) end end).(foo))

foo 提取出來:cas

baz = fn f ->
    fn x -> x.(x) end.(
        (
            fn i ->
                fn g ->
                    i.(fn x -> g.(g).(x) end)
                end
            end
        ).(f)
    )
end.(foo)

化簡內容:參數傳遞

baz = fn f ->
    fn x -> x.(x) end.(
        fn g -> f.(fn x -> g.(g).(x) end) end 
    )
end.(foo)

替換一下參數名,最終咱們獲得 Y 組合子:

y = fn f ->
    fn x -> x.(x) end.(fn x -> f.(fn y -> x.(x).(y) end) end)
end

使用一下試試看:

foo = y.(fn f ->
    fn x ->
        case x do
            0 -> 0
            n -> f.(n-1) + n
        end
    end
end)

foo.(5)
# 15
相關文章
相關標籤/搜索