首页 文章

了解具有多个子句的Elixir函数

提问于
浏览
0

我最近开始学习Elixir . 来自面向对象编程背景我无法理解Elixir函数 .

我正在关注Dave Thomas的书“Programming Elixir”> = 1.6,但我不太明白函数是如何工作的 .

在书中,他有以下例子:

handle_open = fn
  {:ok, file} -> "Read data: #{IO.read(file, :line)}"
  {_,  error} -> "Error: #{:file.format_error(error)}"
end

handle_open.(File.open(​"​​code/intro/hello.exs"​))   ​# this file exists​
-> "Read data: IO.puts \"Hello, World!\"\n"

 handle_open.(File.open(​"​​nonexistent"​))           ​# this one doesn't​
 -> Error: no such file or directory"

我不明白参数是如何工作的 . 隐藏的if,else语句是否隐藏在某处?

2 回答

  • 4

    这里有几件事情,我会尽力覆盖所有这些事情 . 首先,这里使用了两种不同的功能 . 一个是命名函数( File.open ),另一个是您创建的匿名函数,分配给变量 handle_open . 有一点difference in the way both are called .

    当你在 handle_open 函数中调用 File.open 函数时,它基本上意味着你在其结果上调用 handle_open . 但是File.open/2函数本身可以返回两个值:

    • {:ok, file} 如果文件存在

    • {:error, reason} 如果它没有't (or if there' s另一个错误)

    handle_open 函数使用pattern matchingmultiple function clauses来检查响应是什么并返回相应的消息 . 如果给定值"matches a specified pattern"它执行该语句,否则它将检查下一个模式 . 虽然在某种意义上,它类似于 if-else 语句,但更好的类比是case关键字:

    result = File.open("/some/path")
    
    case result do
      {:ok, file} ->
        "The file exists"
    
      {:error, reason} ->
        "There was an error"
    end
    
  • 2

    在elixir中,您可以使用多个子句定义函数,而elixir使用所谓的 pattern matching 来确定要执行的子句 . 正如您所怀疑的那样,当您定义多个函数子句时,您可以有效地创建隐式_54668,例如 if 函数调用中指定的函数参数匹配第一个函数子句的参数, then 执行第一个函数子句, else if 函数调用中指定的函数参数匹配第二个函数子句的参数,然后执行第二个函数子句等 . 这里是一个例子:

    my.exs:

    defmodule My do
    
      def calc(x, 1) do
        x * 3
      end
      def calc(x, 2) do
        x - 4
      end
    
      def test do
        IO.puts calc(5, 1)
        IO.puts calc(5, 2)
      end
    
    end
    

    (我通常不会在函数定义的多个子句之间留一个空行来表示它们都是相同的函数 . )

    在iex中:

    $ iex my.exs
    Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
    Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
    
    iex(1)> My.test
    15
    1
    :ok
    
    iex(2)>
    

    当你调用 calc(5, 1) elixir转到 calc() 的定义时,elixir以第一个函数子句开始,并尝试将args 5, 1 与参数 x, 1 匹配 . 因为参数 x 是一个变量,它可以匹配任何内容,而参数 1 只能匹配 1 . 因此,匹配并且x被赋值为 5 . 在其他语言中,您不能将 1"hello"%{a: 1}:error 等值指定为函数定义中的函数参数 - 而是所有函数参数都必须是变量 .

    类似地,当你调用 calc(5, 2) 时,elixir转到 calc() 的定义,并且elixir以第一个函数子句开始,并尝试将函数调用中的参数 5, 2 与参数 x, 1 匹配 . x 参数可以匹配任何内容,但 1 参数与 2 参数不匹配,因此elixir转到下一个函数子句并尝试将参数 5, 2 与参数 x, 2 匹配 . 这会产生一个匹配,x被赋值为5 .

    Elixir有一些非常有趣的匹配规则 . 以下是您将在某些时候遇到的一些规则,这些规则很难解读:

    • def func(%{] = x) 将匹配任何作为 Map 的参数 - 而不仅仅是空 Map - 并且 Map 将被分配给变量 x . 结构也是如此,例如: def func(%Dog{} = x) 将匹配任何Dog结构 .

    • def func("hello " <> subject) 将匹配以 "hello " 开头的任何字符串参数,并且字符串的其余部分将分配给 subject 变量 .

    Elixir还允许您定义 anonymous functions with multiple clauses . 如果您将 test() 更改为:

    def test do
        func = fn 
                  (:ok, x)    -> IO.puts ":ok branch, x = #{x}"
                  (y, :error) -> IO.puts ":error branch, y = #{y}"
               end
    
        func.("hello", :error)
        func.(:ok, 10)
      end
    

    然后在iex中你会看到:

    ~/elixir_programs$ iex my.exs
    Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
    Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
    
    iex(1)> My.test()  
    :error branch, y = hello
    :ok branch, x = 10
    :ok
    
    iex(2)>
    

    匹配与命名函数一样工作:elixir按顺序查看函数子句,并尝试将函数调用中的参数与函数子句中的参数进行匹配 . 请注意,在代码中定义函数子句的顺序可能很重要 . 定义一个永远不会执行的函数子句很容易,例如:

    func = fn 
              (x)    ->  ...
              (:ok)  ->  ...
           end
    

    因为第一个函数子句中的参数x将匹配任何参数,所以第二个函数子句永远不会执行 . 幸运的是,如果你这样做,长生不老药会警告你 .

    并且,因为elixir是一种函数式语言,所以不显示递归示例是不容错过的 . 将以下 count() 定义添加到My模块:

    def count(0) do
        :ok
      end
      def count(n) when n > 0 do
        Process.sleep 1_000  #sleep for 1 second
        IO.puts n
        count(n-1)
      end
    

    然后在iex中:

    ~/elixir_programs$ iex my.exs 
     Erlang/OTP 20 [erts-9.3] [source]
     [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe]
     [kernel-poll:false] Interactive Elixir (1.6.6) - press Ctrl+C to exit
     (type h() ENTER for help)
    
    iex(1)> My.count(10)
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    :ok
    
    iex(2)>
    

    当代码调用 count(n-1) 时,elixir转到 count() 的第一个函数子句,然后尝试匹配参数 n-1 的值与函数子句中的参数 0 匹配 . 如果没有匹配,elixir会尝试第二个函数子句 . 结果,第二个函数子句保持匹配函数调用 count(n-1) 直到全部输出数字包括1,因此 n-1 在函数调用中等于0,导致函数调用 count(0) ,它最终匹配第一个函数子句,并且第一个函数子句不输出任何内容,也不调用自身,所以递归结束 .

    在灵药中学习的关键是:

    • 模式匹配

    • 递归

    • 产生其他进程

    祝好运!

相关问题