Почему процедуры высшего порядка? - PullRequest
5 голосов
/ 07 мая 2009

Так что, если язык обеспечивает процедуру более высокого порядка, тогда у меня может быть процедура, которая возвращает процедуру. Что-то вроде:

(define (Proc a b c)
  (lambda (x) ( #| method body here in terms of a b c and x |# )))

Чтобы создать новую процедуру, я бы просто сделал что-то вроде:

(define ProcA (Proc a1 b1 c1)) ; Would create ProcA that has 1 argument

Аналогичная задача может быть выполнена на языке, который не поддерживает процедуру более высокого порядка, путем определения Proc, который принимает 4 вместо 3 аргументов, и вызова этой процедуры для определения ProcA, например:

(define (Proc a b c x) ( #| method body -- does not return any procedure |# )
(define (ProcA x) (Proc a1 b1 c1 x))

Так почему в процедуре высшего порядка так много шума? Я что-то упустил?

Ответы [ 8 ]

8 голосов
/ 07 мая 2009

Это хорошее наблюдение, что функция, которая возвращает другую функцию, такая же, как функция, которая принимает два аргумента. Это называется "карри". Другими словами, функция от A до B является доказательством логического следствия того, что A подразумевает B или:

A => B.

Как вы заметили, если A подразумевает, что B подразумевает C, то A и B подразумевают C, или:

(A => (B => C)) <==> ((A, B) => C)

Но функция более высокого порядка не обязательно является функцией, которая возвращает другую функцию. Функция высшего порядка - это функция, которая принимает в качестве аргумента другую функцию . Это важное различие, и HOF - чрезвычайно мощные инструменты программирования.

Например, рассмотрим функцию Haskell:

map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : (map f xs)

Эта функция высшего порядка берет функцию f и применяет ее к каждому элементу в списке. В языках без HOF вы будете делать то, что эта функция делает с циклом или чем-то подобным, но в языке, где есть HOF, вы можете вызвать f для каждого элемента в списке с помощью простого вызова, подобного этому:

map f myList

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

3 голосов
/ 07 мая 2009

Я не буду пытаться резюмировать аргумент здесь, но в Почему функциональное программирование имеет значение , Джон Хьюз утверждает, что функции высшего порядка полезны, потому что они предоставляют более эффективные способы "склеивания" частей программа, и, таким образом, они облегчают повторное использование кода. Примеры написаны на очень старом языке, который больше не используется, но они все еще просты для понимания и довольно убедительны. Чтение статьи Джона - хороший способ получить подробный ответ на ваш вопрос «почему так много размышлений о процедурах высшего порядка».

1 голос
/ 07 мая 2009

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

Скажем, вы пишете алгоритм сортировки со следующим процедурным прототипом:

sort(Array a, void (*fn)(a::element_type, a::element_type));

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

1 голос
/ 07 мая 2009

ОК, но во втором примере вы создаете эту процедуру во время компиляции с предопределенным списком a1, b1 и c1. В первом примере вы создаете его во время выполнения, когда вызываете ProcA, и вы можете создать столько разных, сколько пожелаете, чтобы вы могли делать гораздо больше интересных вещей.

1 голос
/ 07 мая 2009

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

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

0 голосов
/ 26 декабря 2014

Если функция принимает и / или возвращает функцию, она называется функцией высшего порядка (HOF). Для неопытных программистов, происходящих из C, C ++ или Java, функции высшего порядка звучат как волшебство, но они очень просты. Представьте себе простую функцию, которая возвращает результат 2 + 3:

(define (foo) (+ 2 3)) ;; (foo) => 5

Это скучная функция, она всегда добавляет 2 к 3. Что если мы ее обобщим, чтобы она добавляла 2 не только к 3, но и к любому числу, предоставленному пользователем?

(define (foo n) (+ 2 n)) ;; (foo 10) => 12

Когда язык не поддерживает функции высшего порядка, вы вынуждены думать, что функции и значения (например, числа, логические значения, списки) - это две разные вещи. Но функциональное программирование (FP) стирает различия между ними. Представьте, что единственное различие между функцией и значением состоит в том, что функция может быть вызвана, кроме того, что вы можете делать с функцией все, что вы могли бы для 2 или #t или '(a b c): вы могли бы дать ее как аргумент, либо возврат из функции, либо сохранение в переменной, либо помещение его в список. Например, давайте обобщим нашу маленькую функцию, чтобы она не только добавила 2 к n, но умножила 2 на n или применила любую другую функцию, которая бы принимала два числа:

(define (foo f n) (f 2 n))
;; (foo + 10) => 12
;; (foo * 10) => 20
;; (foo expt 10) => 1024

Когда вы понимаете, что функция может обрабатываться так же, как обрабатывается число или строка, анонимные функции (называемые «лямбда-выражения» на жаргонном языке FP) имеют полный смысл. Анонимные функции на самом деле более простые и «нормальные», чем обычные именованные функции, именованные функции - это просто анонимные функции, помещенные в переменную, точно так же, как мы помещаем число в переменную, чтобы использовать его несколько раз.

(+ 2 2) ;; is no different from:
(let ((a 2)) (+ a a))
(lambda (x y) (* x y)) ;; is no different from:
(define (foo x y) (* x y)) ;; which is an abbreviation for:
(define foo (lambda (x y) (* x y))).

Итак, HOF позволяют нам обобщать наши функции, чтобы сделать их сверхгибкими. Если вы посмотрите на свою функцию, увидите логику, стоящую за ней, вы поймете, что если что-то работает с вашими данными, то что-то еще , вероятно, тоже может. Если вы сложите 2 числа вместе, вы, вероятно, умножите их, или вычтите, или возведите в степень одно в другое, или что-то еще. Вместо того, чтобы каждый раз писать новую функцию для каждого случая, вы можете просто принять дополнительный параметр, который должен быть функцией.

В FP мы используем HOF все время, например, при работе со списками. 3 функции - это хлеб с маслом FP: map, filter и foldl. map принимает функцию с 1 аргументом, применяет эту функцию к каждому элементу списка и возвращает новый список с измененными элементами. filter принимает предикат (функция, которая возвращает логическое значение) с 1 аргументом, применяет предикат к каждому элементу списка и возвращает новый список с элементами, которые не удовлетворяют удаленному предикату.

(map (lambda (n) (+ n 1)) '(1 2 3 4 5) ;; '(2 3 4 5 6)
(define (foo n) (+ n 1))
(map foo '(1 2 3 4 5)) ;; '(2 3 4 5 6)
(filter (lambda (n) (> n 3)) '(1 2 3 4 5)) ;; '(4 5)
(define (bar n) (> n 3))
(filter bar '(1 2 3 4 5)) ;; '(4 5)

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

(let ((xs (list (lambda (x) (+ x 1))
                (lambda (x) (* x 2))
                (lambda (x) (- x)))))
  (map (lambda (f) (f 10)) xs)) ;; => (11 20 -10)

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

Примечание: foldl, что аналогично «левому сгибу» или «левому уменьшению», является еще более мощным. Если вы действительно заинтересованы и у вас есть время, пожалуйста, прочитайте первую половину моего ответа, используя сокращение . Хотя он не написан для Scheme / Racket, но вместо этого для Common Lisp / Emacs Lisp, вы все равно можете понять идею, лежащую в основе Fold / Reduce.

0 голосов
/ 07 мая 2009

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

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

Когда C # поддерживал это, я знал, что теперь это стало более распространенным явлением. :)

0 голосов
/ 07 мая 2009

Вам понадобится внутренний класс, чтобы правильно имитировать это. В первом случае Proc замкнут над a, b и c. Во втором случае вызывающая сторона ProcA не может контролировать, как a1, b1 и c1 передаются другой процедуре, он может контролировать только x. Таким образом, то, как вы управляете a1, b1 и c1, заключается в использовании переменных с более высокой областью действия (на уровне модуля или некоторых других), что делает вашу функцию не чистой. В этом случае вы не можете гарантировать, что при одинаковых аргументах между вызовами ProcA будет возвращать один и тот же результат. Где, как и в случае с Proc, вы всегда можете быть уверены, что если вы вызовете его с одинаковыми аргументами, будут получены те же результаты.

...