0

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

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

+1

Второй вопрос лучше всего ответить [Wikipedia] (http://en.wikipedia.org/wiki/First-class_function) или [Rosetta Code] (http://rosettacode.org/wiki/First-class_functions) , Кроме того, вас интересует, например, (вы можете передавать их в качестве параметров, но большинство языков допускает гораздо больше) или преимущества функций как первоклассных значений в целом? – delnan

ответ

3

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

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

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

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

+0

Обратите внимание, что даже указатели функций C могут делать это (хотя и не универсальные или безопасные по типу). Анонимные функции, как в этом примере, связаны друг с другом, но что-то другое. – delnan

+0

Анонимная функция не является строго обязательной для примера. То же самое можно сделать с помощью способности передавать именованные функции, даже если это означает, что вы должны определить их где-то вдали от того места, где они используются. Суть в том, что в таких языках, как Java, которые не позволяют передавать функции в качестве аргументов, вам нужно сделать довольно уродливые хаки (например, обернуть их в класс, чтобы вы могли передать его как аргумент), чтобы получить такая же функциональность. –

1

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

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

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

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

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

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

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

+0

C++ 0x добавляет анонимные функции и замыкания. Но этот вопрос касается не закрытия. – delnan

1

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

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 новая анонимная функция одного аргумента, который сам по себе относится к gx и 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; и другие. Это полезная функция, поэтому неудивительно, что она так широко распространена.

Смежные вопросы