将函数传递给get_in()时如何工作?

时间:2018-08-03 23:57:37

标签: elixir

在“ 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"]

我对示例有一些疑问:

  1. 您传递给get_in()的函数由Elixir调用,并且Elixir传递给该函数的第一个参数是原子:get。这有什么用?

  2. Elixir传递给函数的第三个参数是绑定到next_fn的函数。它在文档中的哪里说明该函数需要多少个参数?该功能有什么作用?我们应该如何使用next_fn?在我看来,for构造已经在列表中的每个映射上进行了迭代,因此名称next_fn甚至意味着什么?是否使用next_fn标记行以作进一步考虑?

  3. 结果列表中的零是哪里来的?

而且,我要说的是:该示例是我在任何编程书籍中都看到过的最糟糕的示例之一,因为对该示例的讨论不够,并且get_in()的文档很烂。这意味着至少有三个人不了解get_in():我,戴夫·托马斯(Dave Thomas)和编写文档的人-因为如果您无法解释某些内容,那么他​​们自己就不会理解。 / p>

编辑:我在source code中找到了这个

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

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

1 个答案:

答案 0 :(得分: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()的调用将返回与:name中的row键对应的值。我认为,如果将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

如果:name包含“ r”,则该代码仅在row中搜索row.language键。

最后,以下代码片段显示了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。 / p>