首页 文章

当你传递一个函数时,get_in()如何工作?

提问于
浏览
1

在“Programming Elixir 1.6”中,有这个例子:

authors = [
  %{name: "José", language: "Elixir"},
  %{name: "Matz", language: "Ruby"},
  %{name: "Larry", language: "Perl"}
]

languages_with_an_r = fn (:get, collection, next_fn) ->
  for row <- collection do
    if String.contains?(row.language, "r") do
      next_fn.(row)
    end
  end
end

IO.inspect get_in(authors, [languages_with_an_r, :name])
#=> ["José", nil, "Larry"]

我对这个例子有一些疑问:

  • 传递给 get_in() 的函数由Elixir调用,而Elixir传递给函数的第一个参数是atom :get . 这有用吗?

  • Elixir传递给函数的第三个参数是一个绑定到 next_fn 的函数 . 文档中的哪个位置表示函数需要多少个参数?这个功能有什么作用?我们该如何使用 next_fn ?在我看来, for 构造已经遍历列表中的每个 Map ,那么 next_fn 甚至意味着什么呢? next_fn 曾经以某种方式标记一行以供进一步考虑吗?

  • 结果列表中的nil来自哪里?

而且,我在任何编程书中都看到了 - 因为没有充分讨论这个例子,而且 get_in() 的文档很糟糕 . 这意味着至少有三个人不理解 get_in() :我,戴夫托马斯,以及编写文档的人 - 因为如果你能够自己理解它 .

编辑:我在source code中发现了这个:

def get_in(data, [h | t]) when is_function(h), 
  do: h.(:get, data, &get_in(&1, t))

&1 指的是什么? data ?为什么不只使用 data 呢?

1 回答

  • 1

    好吧,我一直在玩iex:

    iex(13)> mymax = fn x -> &max(&1, x) end
    #Function<6.99386804/1 in :erl_eval.expr/5>
    
    iex(15)> max_versus_3 = mymax.(3)
    #Function<6.99386804/1 in :erl_eval.expr/5>
    
    iex(16)> max_versus_3.(4)
    4
    
    iex(17)> max_versus_3.(2)
    3
    

    看起来语法 &max(&1, 3) 返回匿名函数:

    fn (arg) -> max(&1, 3)
    

    当周围范围中的x = 3时,语法 &max(&1, x) 也将返回该函数:

    fn (arg) -> max(&1, 3)
    

    &1 将成为调用匿名函数的任何单个arg .

    在这个例子中:

    authors = [
      %{name: "José", language: "Elixir"},
      %{name: "Matz", language: "Ruby"},
      %{name: "Larry", language: "Perl"}
    ]
    
    languages_with_an_r = fn (:get, collection, next_fn) ->
      for row <- collection do
        if String.contains?(row.language, "r") do
          next_fn.(row)
        end
      end
    end
    
    IO.inspect get_in(authors, [languages_with_an_r, :name])
    #=> ["José", nil, "Larry"]
    

    在这里调用 get_in()

    IO.inspect get_in(authors, [languages_with_an_r, :name])
    

    匹配源代码中的以下函数定义:

    def get_in(data, [h | t]) when is_function(h), 
        do: h.(:get, data, &get_in(&1, t))
    

    这会创建以下绑定:

    data = authors
    h = languages_with_an_r
    t = [:name]`
    

    然后Elixir执行函数体并调用:

    h.(:get, data, &get_in(&1, t))
    

    这相当于:

    languages_with_an_r.(
         :get, 
         authors, 
         fn (arg) -> get_in(&1, [:name])
    

    这创造了绑定:

    next_fn = fn (arg) -> get_in(&1, [:name])
    

    因此,在作者的例子中,该行:

    next_fn.(row)
    

    相当于调用:

    fn (row) -> get_in(&1, [:name])
    

    这导致 get_in() 使用这些参数执行:

    get_in(row, [:name])
    

    并且对 get_in() 的调用返回 row 中与 :name 键对应的值 . 我认为如果重命名 languages_with_an_r() 定义中的参数变量,作者的例子会更清楚:

    languages_with_an_r = fn (:get, collection, search_for_next_key_in) ->
          for row <- collection do
            if String.contains?(row.language, "r") do
              search_for_next_key_in.(row)
            end
          end
        end
    

    如果 row.language 包含"r",该代码将仅搜索 row 中的 :name 键 .

    最后,以下代码段显示 nil 来自哪里:

    iex(5)> for x <- [1, 2, 3] do       
    ...(5)> if x == 1_000_000, do: x+1    
    ...(5)> end
    [nil, nil, nil]
    

    就像在Ruby中一样,似乎Elixir中的 do block 返回已计算的最后一个表达式的值 . 并且,当 do block 不评估表达式时, do block 默认返回 nil . 因此,如果 row.language 不包含"r",则跳过if语句, do block 不计算任何表达式,因此默认情况下 do block 返回nil .

相关问题