【问题标题】:Understanding pattern matching in Elixir function parameters了解 Elixir 函数参数中的模式匹配
【发布时间】:2019-11-07 20:24:08
【问题描述】:

在“Elixir in Action”一书中,其中一个示例的功能使我对模式匹配的理解产生了障碍:

def add_entry(
    %TodoList{entries: entries, auto_id: auto_id} = todo_list,
    entry
  ) do
    entry = Map.put(entry, :id, auto_id)
    new_entries = HashDict.put(entries, auto_id, entry)

    %TodoList{todo_list |
      entries: new_entries,
      auto_id: auto_id + 1
    }
 end

第一个参数%TodoList{entries: entries, auto_id: auto_id} = todo_list,书中解释了“...此外,您将整个实例保存在todo_list 变量中”

这让我很困惑,因为我认为变量会绑定在“=”模式匹配运算符的左侧。有人可以帮助解释第一个参数发生了什么以及如何在函数体内使用传入的值吗?

【问题讨论】:

    标签: pattern-matching elixir


    【解决方案1】:

    我认为变量会绑定在 '=' 模式匹配运算符的左侧

    没错,在这种情况下entriesauto_id 变量是绑定的。右侧todo_list 从参数绑定到函数。

    就像这样做:

    iex(1)> foobar = %{foo: "foo", bar: "bar"}
    %{bar: "bar", foo: "foo"}
    iex(2)> %{foo: matched} = foobar
    %{bar: "bar", foo: "foo"}
    iex(3)> matched
    "foo"
    

    在函数签名中执行此操作的唯一区别是,定义右侧的第一步是自动处理的。

    正如书中所说,您可以像这样定义函数签名:

    def do_something_with_foo(%{foo: matched} = original)
    

    上面已经解释过了,matchedoriginal 在函数体中都可用。如果你关心匹配的值,你可以省略右边:

    def do_something_with_foo(%{foo: matched})
    

    在这种情况下,只有匹配的值matched 可用。匹配仍然会发生,但是作为第一个参数传递给函数的数据结构,它被隐式用作右侧,就像您使用了 = 一样,没有绑定到变量。

    【讨论】:

    • 可能值得一提的是,def do_smth(matched = %{foo: original}) 也可能是真正的魔法发生的地方 :) 尝试将 def f(%{al: al} = a = %{ar: ar}, do: IO.inspect({al, a, ar}) 称为 f(%{al: :left, ar: :right})。如果任务是从右到左完成的,我们会在那里得到MatchError,但a完全没问题。
    • 还有:%{a_left: a_left} = a_full = %{} = a_full = %{a_right: a_right} = %{a_left: 42, a_right: 42}。看起来在对函数的调用中,erlang 分配了一个隐含的_arg,然后将链中的所有内容与它匹配。
    【解决方案2】:

    带变量:

    iex(2)> x = %{a: 1, b: 2}   
    %{a: 1, b: 2}
    
    iex(3)>  %{a: 1, b: 2} = y
    ** (CompileError) iex:3: undefined function y/0
    

    带函数参数变量:

    defmodule A do
    
      def go1(z = %{a: a}) do
        IO.inspect z
        IO.puts a
      end
    
      def go2(%{a: a} = z) do
        IO.inspect z
        IO.puts a
      end
    
    end
    

    在 iex 中:

    iex(4)> c "a.ex"  
    warning: redefining module A (current version defined in memory)
      a.ex:1
    [A] 
    
    iex(5)> map = %{a: 1, b: 2} 
    %{a: 1, b: 2}
    
    iex(6)> A.go1(map)
    %{a: 1, b: 2}
    1
    :ok
    
    iex(7)> A.go2(map)
    %{a: 1, b: 2}
    1
    :ok
    

    函数参数与函数参数变量模式匹配。而且,在 elixir 函数中,参数可以是常量,例如1 或一个原子、映射、元组等——不仅仅是一个变量,如 x、y 或 z。以下是go1() 的工作原理:

        A.go1(%{a: 1 b: 2})
              |-----+----|
                    |
                    | %{a: a} = %{a: 1 b: 2} 
                    V
    def go1(z = %{a: a}) do
    

    “参数变量”是%{a: a},它与函数参数%{a: 1 b: 2}相匹配,将a绑定到1。然后,你可能认为你得到了模式匹配:z = %{a: 1},然而,模式匹配%{a: a} = %{a: 1 b: 2}实际上返回了右手边:

    iex(10)> %{a: a} = %{a: 1, b: 2}    
    %{a: 1, b: 2}
    

    因此,您会得到模式匹配:z = %{a: 1, b: 2}。这是另一个演示:

    iex(13)> z = %{a: a} = %{a: 1, b: 2}
    %{a: 1, b: 2}
    
    iex(14)> a
    1
    
    iex(15)> z
    %{a: 1, b: 2}
    

    go2() 的工作原理如下:

          A.go1(%{a: 1 b: 2})
                |-----+----|
                      |
                      | z = %{a: 1, b: 2}
                      V
    def go2(%{a: a} = z)
    

    z 是参数变量,它与函数参数%{a: 1 b: 2} 匹配。匹配 z = %{a: 1 b: 2} 返回右侧:

    iex(10)> z = %{a: 1, b: 2}
    %{a: 1, b: 2}
    

    所以,接下来你会得到模式匹配:%{a: a} = %{a: 1, b: 2},它将a 绑定到1

    因此,一切都是一致的:对于每个模式匹配,包含变量的模式在= 的左侧,而值在右侧。如果您正在寻找一个规则,那就是:在函数定义的参数列表中,= 符号右侧的内容是“参数变量”。左边的东西是在“参数变量”与函数参数匹配后将匹配的模式(或者正如您在其他语言中所说:“在函数参数分配给参数变量之后”)。

    【讨论】:

    • 前一个例子 (A.go1(%{a: 1 b: 2})) 虽然不是很准确。如果 “那么你得到模式匹配:z = %{a: 1}。” 是一个东西,z 将被绑定到 %{a: 1},而它被绑定到调用中的整个参数。请参阅我的 cmets 以了解另一个答案。
    • 好收获!固定。
    【解决方案3】:

    当您指定%{map_thing: stuff} = map_var 你是说我希望这个变量包含变量map_thing,我希望你把这个映射的全部内容放入变量map_var。您可以使用它在该映射中明确指定所需的键,还可以获取所有可选键并将它们“绑定”到您的 map_var

    你可以用它做各种有用的事情,比如创建一种假警卫以防万一

    case some_var do
    %MyStruct{} = struct_var ->
     # we are saying we expect this var to be of the `MyStruct` variety.
    other_case ->
      do_something_else()
    end
    

    【讨论】:

      猜你喜欢
      • 2020-11-25
      • 2017-03-16
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 2012-09-02
      • 1970-01-01
      • 2018-11-24
      相关资源
      最近更新 更多