使用 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
相关文章
相关标签/搜索