Мне просто интересно, какие преимущества имеют языки, позволяющие передавать функции через параметры? И, надеюсь, пара примеров для этого.Передача подпрограмм/функций через параметры
Какие популярные языки программирования позволяют это?
Мне просто интересно, какие преимущества имеют языки, позволяющие передавать функции через параметры? И, надеюсь, пара примеров для этого.Передача подпрограмм/функций через параметры
Какие популярные языки программирования позволяют это?
Предположим, у меня есть коллекция объектов, и я хочу отсортировать их по определенным критериям.
Если я могу передать функцию в функцию, это легко:
collection.sort((a, b) => a.SomeProperty.compareTo(b.SomeProperty));
Если я не могу, то для того, чтобы сделать что-то вроде этого я в основном должен переопределять алгоритм сортировки каждый раз, когда я хочу сортировать по разным критериям.
Обратите внимание, что даже указатели функций C могут делать это (хотя и не универсальные или безопасные по типу). Анонимные функции, как в этом примере, связаны друг с другом, но что-то другое. – delnan
Анонимная функция не является строго обязательной для примера. То же самое можно сделать с помощью способности передавать именованные функции, даже если это означает, что вы должны определить их где-то вдали от того места, где они используются. Суть в том, что в таких языках, как Java, которые не позволяют передавать функции в качестве аргументов, вам нужно сделать довольно уродливые хаки (например, обернуть их в класс, чтобы вы могли передать его как аргумент), чтобы получить такая же функциональность. –
Преимущество в том, что вы можете параметризовать функции над вещами, которые не просто выражаются в виде простых значений.
Например, если вы хотите найти элемент, который удовлетворяет определенному предикату в коллекции или вы хотите узнать, удовлетворяют ли все элементы в коллекции предикату, то предикат наиболее естественно выражается как функция из элемента type to bool.
Другим действительно распространенным примером является функция сортировки, которая выполняет функцию сравнения в качестве аргумента, поэтому элементы коллекции могут быть отсортированы с помощью настраиваемого ключа сортировки.
Конечно, на языках, которые не позволяют функциям в качестве аргументов, но которые имеют объектную систему (читайте: в java), вы можете обойти это, используя объект, который реализует определенный интерфейс для функции сортировки.
Какие популярные языки программирования позволяют [передавать функции в качестве аргументов]?
C, C++, C#, Objective C, Python, Ruby, Perl, PHP, Javascript и в основном все функциональные или функциональные языки.
Следует отметить, что C и C++ (до C++ 0x), в отличие от других языков в списке, не имеют замыканий.
C++ 0x добавляет анонимные функции и замыкания. Но этот вопрос касается не закрытия. – delnan
Преимущество возможности передачи функций другим функциям состоит в том, что он позволяет писать более чистый, более общий код. Например, рассмотрим достаточно реалистичную функцию, которая суммирует все числа в списке:
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
); в противном случае мы применяем функцию к (a) первому элементу списка и (b) результат складывания остальной части списка. В более традиционном языке, this'd выглядеть как
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
(здесь (+)
и (*)
являются два-аргумента функции, которые добавляют и умножьте их аргументы соответственно.) Вы не можете сделать это без первоклассных функций. (Другая вещь, которую я делаю, - это оставить последний аргумент, это называется currying, и это довольно удобно. Идея состоит в том, что если я не дам 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
}
Но это явно abstractable: почти точно такой же код будет работать, если вы хотите, чтобы свести на нет все элементы ваш список, или если у вас есть список писем и вы хотите получить отправителей или что-то еще. Итак, мы абстрагируемся:
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++ do, вроде: вы не можете писать функции в строке, но вы можете передавать указатели функций. Это не очень распространено на любом языке, хотя (даже меньше в C). Любой язык OO может, если вы определяете класс, который имеет единственный метод, который является функцией, которую вы хотите, но это ужасно неуклюже. Тем не менее, это то, что делает Java. С другой стороны, C# имеет функции, которые могут передаваться как параметры. Ruby (у которого также есть «блоки», которые являются специальным синтаксисом для определенных случаев использования), Python, Perl и JavaScript поддерживают все функции передачи, как и каждый язык функционального программирования: Lisps (Scheme, Common Lisp, Clojure, ...); семейство ML (SML, OCaml, ...); Haskell (который может быть объединен с семейством ML); Scala; и другие. Это полезная функция, поэтому неудивительно, что она так широко распространена.
Второй вопрос лучше всего ответить [Wikipedia] (http://en.wikipedia.org/wiki/First-class_function) или [Rosetta Code] (http://rosettacode.org/wiki/First-class_functions) , Кроме того, вас интересует, например, (вы можете передавать их в качестве параметров, но большинство языков допускает гораздо больше) или преимущества функций как первоклассных значений в целом? – delnan