3

我在Elixir的示例中看到了这段代码:

defmodule Recursion do
  def print_multiple_times(msg, n) when n <= 1 do
    IO.puts msg
  end

  def print_multiple_times(msg, n) do
    IO.puts msg
    print_multiple_times(msg, n - 1)
  end
end

Recursion.print_multiple_times("Hello!", 3)

我在这里看到用不同的参数定义了两次相同的函数,我想了解这种技术。

我可以将它们视为重载函数吗?

它是具有不同行为的单个函数还是这两个不同的函数,例如print_only_onceprint_multiple_times

这些功能是否相互关联?

4

2 回答 2

3

通常在函数式语言中,函数由子句定义。例如,用命令式语言实现斐波那契的一种方法是以下代码(不是最佳实现):

def fibonacci(n):
  if n < 0:
    return None
  if n == 0 or n == 1:
    return 1
  else:
    return fibonacci(n - 1) + fibonacci(n - 2)

要在 Elixir 中定义函数,您需要执行以下操作:

defmodule Test do
  def fibonacci(0), do: 1
  def fibonacci(1), do: 1
  def fibonacci(n) when n > 1 do
    fibonacci(n-1) + fibonacci(n - 2)
  end
  def fibonacci(_), do: nil
end

Test.fibonacci/1只是一种功能。一个有四个子句和 arity 为 1 的函数。

  • 第一个子句仅在数字为 0 时匹配。
  • 第二个子句仅在数字为 1 时匹配。
  • 第三个子句匹配任何大于 1 的数字。
  • 最后一个子句匹配任何内容(_当变量的值不会在子句内使用或与匹配无关时使用)。

这些子句按照它们声明的顺序进行评估,因此 forTest.fibonacci(2)将在前 2 个子句中失败并与第三个子句匹配,因为2 > 1.

将子句视为更强大的if陈述。这样代码看起来更干净。并且对于递归非常有用。例如,一个地图实现(语言已经提供了一个Enum.map/2):

defmodule Test do
  def map([], _), do: []
  def map([x | xs], f) when is_function(f) do
    [f.(x) | map(xs, f)]
  end
end
  • 第一个子句匹配一个空列表。无需应用功能。
  • 第二个子句匹配一个列表,其中第一个元素 (head) 是x并且列表的其余部分 (tail) 是xs并且f是一个函数。它将函数应用于第一个元素并递归地调用 map 与列表的其余部分。

调用Test.map([1,2,3], fn x -> x * 2 end)将为您提供以下输出[2, 4, 6]

因此,Elixir 中的函数由一个或多个子句定义,其中每个子句都具有与其余子句相同的数量。

我希望这回答了你的问题。

于 2016-07-12T11:01:57.470 回答
1

在您发布的示例中,函数的两个定义具有相同数量的参数:2,这个“何时”事物是一个守卫,但您也可以有许多参数的定义。首先,守卫——它们被用来表达不能被写成仅仅匹配的东西,比如下面的第二行:

def fac(0), do: 1
def fac(n), when n<0 do: "no factorial for negative numbers!"
def fac(n), do: n*fac(n-1)

- 因为不可能仅通过相等/匹配来表示负数。

顺便说一句,这个fac是一个单一的定义,只有三种情况。请注意在参数位置使用常量“0”的酷炫 :) 您可以认为这是更好的编写方式:

def fac(n) do
  if n==0, do: 1, else: if n<0, do: "no factorial!", else: n*fac(n-1)
end

或开关盒(甚至看起来非常接近上述):

def fa(n) do
  case n do
    0 -> 1
    n when n>0 -> n*fa(n-1)
    _ -> "no no no"
  end
end

只有“看起来更花哨”。实际上,事实证明某些定义(例如解析器、小型解释器)在前者中看起来比后一种风格好得多。Nb 守卫表达式非常有限(我认为您不能在守卫中使用自己的函数)。

现在是真实的,不同数量的参数 - 看看这个!

def mutant(a), do: a*a
def mutant(a,b), do: a*b
def mutant(a,b,c), do: mutant(a,b)+mutant(c)

例如

iex(1)> Lol.mutant(2)
4
iex(2)> Lol.mutant(2,3)
6
iex(3)> Lol.mutant(2,3,4)
22

它的工作原理有点类似于方案中的 (lambda arg ...) —— 将突变体视为将其所有参数作为一个列表并对其进行匹配。但这一次,elixir 将mutant 视为3 个函数,mutant/1mutant/2mutant/3并会这样称呼它们。

所以,回答你的问题:这些不像重载函数,而是分散/碎片化的定义。您会在 miranda、haskell 或 sml 等函数式语言中看到类似的语言。

于 2016-07-12T11:21:10.403 回答