Передача подпрограмм / функций через параметры - PullRequest
0 голосов
/ 09 декабря 2010

Мне просто интересно, каковы преимущества для языков, которые позволяют функциям передаваться через параметры? И, надеюсь, несколько примеров, чтобы пойти с этим.

Какие популярные языки программирования позволяют это?

Ответы [ 3 ]

3 голосов
/ 09 декабря 2010

Предположим, у меня есть коллекция объектов, и я хочу отсортировать их по некоторым критериям.

Если я могу передать функцию в функцию, это просто:

collection.sort((a, b) => a.SomeProperty.compareTo(b.SomeProperty));

Если я не могу, то для того, чтобы сделать что-то подобное, мне в основном приходится переопределять алгоритм сортировки каждый раз, когда я хочу отсортировать по различным критериям.

1 голос
/ 09 декабря 2010

Преимущество возможности передавать функции другим функциям заключается в том, что они позволяют писать более понятный и более общий код.Например, рассмотрим достаточно реалистичную функцию, которая суммирует все числа в списке:

sum []     = 0
sum (x:xs) = x + sum xs

Это синтаксис Haskell, который может быть вам незнаком;две строки определяют два разных случая, в зависимости от того, является ли ввод пустым списком, в этом случае сумма равна нулю, или она x добавляется к списку xs, в этом случае сумма равна x плюссумма xs.На более традиционном языке (не на каком-либо конкретном) у вас будет

function sum(xs) {
  var total = 0
  for x in xs {
    total = x + total
  }
  return total
}

Теперь предположим, что мы хотим найти произведение чисел в списке, вместо этого:

product []     = 1
product (x:xs) = x * product xs

На традиционном языке это выглядело бы как

function product(xs) {
  var total = 1
  for x in xs {
    total = x * total
  }
  return total
}

Интересно, что эти две функции выглядят практически одинаково.Единственное отличие состоит в том, что 0 заменяется на 1, а + заменяется на *.И действительно, получается, что мы можем обобщить как sum, так и +:

foldr f i []     = i
foldr f i (x:xs) = f x (foldr f i xs)

Здесь foldr принимает три аргумента: функция с двумя аргументами f, константа i и список.Если список пуст, мы возвращаем константу ( например , sum [] = 0);в противном случае мы применяем функцию к (а) первому элементу списка и (б) результат свертывания остальной части списка.На более традиционном языке это будет выглядеть примерно так:

function foldr(f,i,xs)
  var result = i
  for x in xs {
    result = f(x, result)
  }
  return result
}

Это означает, что мы можем упростить sum и product до простого

sum     = foldr (+) 0
product = foldr (*) 1

(Здесь (+) и (*) - это функции с двумя аргументами, которые добавляют и умножают свои аргументы соответственно.) Вы не можете сделать это без функций первого класса.(Другая вещь, которую я делаю, - это опускание последнего аргумента; это называется каррированием, и это довольно удобно. Идея состоит в том, что если я не передам foldr все его аргументы, он возвращает функцию, которая ожидаетостальные из них. Но если вас это смущает, представьте, что в определениях сказано: sum xs = foldr (+) 0 xs.)

Но здесь все может стать интереснее.Предположим, у вас есть список чисел, и вы хотите вычислить квадрат каждого числа в списке:

squares []     = []
squares (x:xs) = (x^2) : squares xs

function squares(xs) {
  var result = []
  for x in xs {
    result = result ++ [x^2]
  }
  return result
}

Но это явно абстрагируемо: почти точно такой же код будет работать, если вы хотите отрицать каждоеэлемент вашего списка, или если у вас есть список электронных писем и вы хотите получить отправителей, или что-то еще.Итак, мы абстрагируем:

map f []     = []
map f (x:xs) = f x : map f xs

function map(f,xs) {
  var result = []
  for x in xs {
    result = result ++ [f(x)]
  }
  return result
}

squares      = map (^2) # (^2) is the function which squares its argument.
negations    = map negate
emailSenders = map getSender

Но что интересно, мы также можем реализовать map в терминах нашего предыдущего foldr.Во-первых, нам нужно определить состав функции, .:

f . g = \x -> f (g x)

. Здесь говорится, что композиция f и g - это новая анонимная функция с одним аргументом, которая сама применяет g кx и f к результату.И теперь мы можем определить map:

map f = foldr ((:) . f) []

Здесь (:) - это функция, которая берет элемент и список и возвращает этот элемент, добавленный к списку.(:) . f - это то же самое, что и \x -> (:) (f x), которое (согласно упомянутому правилу каррирования) совпадает с \x xs -> f x : xs.Другими словами, на каждом шаге сгиба добавьте f x к тому, что мы имеем до сих пор.(Это не дает нам обратную сторону map, потому что foldr работает как бы "наизнанку".)

В этом определении map используется функция высшего порядка .для построения функции она переходит к foldr.Так много функций передается в качестве параметров!И это позволяет нам делать такие вещи, как запись

f &&& g = \x -> (f x, g x)

, а затем использовать это для записи

sendersAndRecipients = map (getSender &&& getRecipient) . fetchEmails

Вы получаете большую мощность, имея возможность обрабатывать функции как значения.Вы можете написать общие процедуры, такие как map или &&&, которые позволят вам написать краткий, но читаемый код позже.Передача функций также полезна для обратных вызовов: sendMessage(theMessage,fn), где fn - функция, которая запускается при получении ответа.Это просто очень естественный способ работы.

Что касается того, какие языки поддерживают это: честно, Википедия знала бы лучше, чем я. Но я сделаю это. C и C ++ делают что-то вроде: вы не можете писать функции in-line, но вы можете передавать указатели на функции. Это не очень распространено в любом языке (хотя и в C). Любой ОО-язык может, если вы определите класс, у которого есть единственный метод, который является той функцией, которую вы хотите, но это ужасно неуклюже. Тем не менее, это то, что делает Java. C #, с другой стороны, на самом деле имеет функции, которые могут быть переданы в качестве параметров. Ruby (который также имеет «блоки», которые являются специальным синтаксисом для определенных случаев использования), Python, Perl и JavaScript поддерживают передачу функций, как и любой функциональный язык программирования: Lisps (Scheme, Common Lisp, Clojure,… ); семья ML (SML, OCaml,…); Haskell (который может быть связан с семьей ML); Scala; и другие. Это полезная функция, поэтому неудивительно, что она так широко распространена.

1 голос
/ 09 декабря 2010

Преимущество состоит в том, что вы можете параметризовать функции над вещами, которые нелегко выразить в виде простых значений.

Например, если вы хотите найти элемент, который удовлетворяет определенному предикату в коллекции, или вы хотитечтобы выяснить, все ли элементы в коллекции удовлетворяют предикату, предикат наиболее естественно выражается в виде функции от типа элемента до bool.

Другим действительно распространенным примером является функция сортировки, которая принимает функцию сравнения в качестве своей.аргумент, поэтому элементы коллекции могут быть отсортированы с помощью специального ключа сортировки.

Конечно, в языках, которые не допускают функции в качестве аргументов, но которые имеют объектную систему (читай: в java),Вы можете обойти это, используя объект, который реализует определенный интерфейс к функции сортировки.

Какие популярные языки программирования допускают [передачу функций в качестве аргументов]?

C, C ++, C #, Objective C, Python, Ruby, Perl, PHP, Javascript, и в основном все веселоCtional или функциональные языки.

Следует отметить, что C и C ++ (до C ++ 0x), в отличие от других языков в списке, не имеют замыканий.

...