Функции с одинаковыми именами, но разными аргументами в функциональных языках

Я вижу этот код в примере 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_once и print_multiple_times?

Эти функции как-то связаны или нет?


person Sergei Basharov    schedule 12.07.2016    source источник
comment
На самом деле речь идет об сопоставлении с образцом и защитных предложениях. Выбирается реализация функции, которая лучше всего соответствует аргументам, включая оценку when Guard. Это одна и та же функция, просто она имеет несколько реализаций.   -  person deceze♦    schedule 12.07.2016
comment
Независимо от того, имеет ли ваша функция другую сигнатуру, это нормально, но помните, что другой доступ (def или defp) не разрешен, когда сигнатура одинакова.   -  person PatNowak    schedule 12.07.2016


Ответы (2)


Обычно в функциональных языках функция определяется предложениями. Например, одним из способов реализации Фибоначчи на императивном языке может быть следующий код (не самая лучшая реализация):

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

Чтобы определить функцию в Эликсире, вы должны сделать следующее:

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 — это только одна функция. Функция с четырьмя предложениями и арностью 1.

  • Первое предложение соответствует только тогда, когда число равно 0.
  • Второе предложение соответствует только тогда, когда число равно 1.
  • Третье предложение соответствует любому числу больше 1.
  • Последнее предложение соответствует чему угодно (_ используется, когда значение переменной не будет использоваться внутри предложения или не имеет отношения к совпадению).

Предложения оцениваются в том порядке, в котором они объявлены, поэтому для Test.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
  • Первое предложение соответствует пустому списку. Нет необходимости применять функцию.
  • Второе предложение соответствует списку, в котором первый элемент (голова) равен x, а остальная часть списка (хвост) — xs, а f — функция. Он применяет функцию к первому элементу и рекурсивно вызывает карту с остальной частью списка.

Вызов Test.map([1,2,3], fn x -> x * 2 end) даст вам следующий вывод [2, 4, 6]

Итак, функция в Эликсире определяется одним или несколькими предложениями, где каждое предложение имеет ту же арность, что и остальные.

Надеюсь, это ответит на ваш вопрос.

person Alex de Sousa    schedule 12.07.2016
comment
Небольшой комментарий: обычно в функциональных языках функция определяется предложениями. Это верно для Haskell и Standard ML, но не для OCaml или Scheme, например. - person FPstudent; 13.07.2016

В примере, который вы опубликовали, оба определения функции имеют одинаковое количество аргументов: 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)

e.g.

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

Он работает немного похоже на (лямбда-аргумент ...) в схеме - представьте, что мутант принимает все свои аргументы в виде списка и сопоставляет его. Но на этот раз elixir рассматривает mutant как 3 функции, mutant/1, mutant/2 и mutant/3, и будет ссылаться на них как такой.

Итак, чтобы ответить на ваш вопрос: это не перегруженные функции, а скорее разбросанные/фрагментированные определения. Вы видите похожие в функциональных языках, таких как miranda, haskell или sml.

person dercz    schedule 12.07.2016