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

在“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)

2 years ago

好吧,我一直在玩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 .