Каковы некоторые интересные применения функций более высокого порядка?

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

знаете ли вы примеры таких интересных функций?

может быть, функции, которые возвращают функции, функции, возвращающие списки функций (?), п.

Я был бы признателен за примеры в Haskell, который является языком, который я в настоящее время изучаю:)

14 ответов


Ну, вы заметили, что у Haskell нет синтаксиса для циклов? Нет!--1--> или do или for. Потому что все это просто функции более высокого порядка:

 map :: (a -> b) -> [a] -> [b]

 foldr :: (a -> b -> b) -> b -> [a] -> b

 filter :: (a -> Bool) -> [a] -> [a]

 unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

 iterate :: (a -> a) -> a -> [a]

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

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

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

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


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

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

в стратегия pattern-еще один пример схемы, которая часто передает объекты в качестве аргументов в качестве замены того, что на самом деле предназначено, функций.

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

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


Я действительно начал чувствовать силу, когда узнал, что функция может быть частью структуры данных. Вот " потребительская монада "(technobabble: free monad over (i ->)).

data Coro i a
    = Return a
    | Consume (i -> Coro i a)

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

Consume $ \x -> Consume $ \y -> Consume $ \z -> Return (x+y+z)

это потребляет три целочисленных входных данных и возвращает их сумму. Вы также могли бы вести себя по разному входы:

sumStream :: Coro Int Int
sumStream = Consume (go 0)
    where
    go accum 0 = Return accum
    go accum n = Consume (\x -> go (accum+x) (n-1))

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

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


Проверьте бумагу даже высшего порядка функции для парсинга или почему кто-нибудь хотите Использовать функцию шестого порядка?' Криса Окасаки. Он написан с использованием ML, но идеи одинаково применимы к Haskell.


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


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

f :: A -> B -> C

...the (->) можно читать как правоассоциативный, показывая, что это на самом деле функция более высокого порядка, возвращающая функцию типа B -> C:

f :: A -> (B -> C)

функция без Карри из двух аргументов вместо этого будет иметь такой тип:

f' :: (A, B) -> C

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


Мартин Escardó обеспечивает интересный пример функции более высокого порядка:

equal :: ((Integer -> Bool) -> Int) -> ((Integer -> Bool) -> Int) -> Bool

учитывая два функционала f, g :: (Integer -> Bool) -> Int, потом equal f g решает, если f и g а (объемно) равен или нет, хотя f и g не имеют конечной области. На самом деле, кодомен,Int, может быть заменен любым типом с разрешимым равенством.

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

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


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

мой Haskell довольно ржавый, поэтому я не могу легко дать вам пример Haskell, но вот упрощенный пример Clojure, который, надеюсь, передает концепция:

(defn make-object [initial-value]
  (let [data (atom {:value initial-value})]
      (fn [op & args]
        (case op 
          :set (swap! data assoc :value (first args))
          :get (:value @data)))))

использование:

(def a (make-object 10))

(a :get)
=> 10

(a :set 40)

(a :get)
=> 40

тот же принцип будет работать в Haskell (за исключением того, что вам, вероятно, придется изменить операцию set, чтобы вернуть новую функцию, так как Haskell является чисто функциональным)


Я особый поклонник memoization более высокого порядка:

memo :: HasTrie t => (t -> a) -> (t -> a)

(учитывая любую функцию, верните memoized версию этой функции. Ограничились тем, что аргументы функции должны быть закодированы в виде дерева.)

Это из http://hackage.haskell.org/package/MemoTrie


здесь есть несколько примеров: http://www.haskell.org/haskellwiki/Higher_order_function

Я бы также рекомендовал эту книгу:http://www.cs.nott.ac.uk / ~gmh/book.html что большое введение ко всему из Хаскелл и покрывает функции более высокого порядка.

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


вот небольшой перефразированный код фрагмент:

rays :: ChessPieceType -> [[(Int, Int)]]
rays Bishop = do
  dx <- [1, -1]
  dy <- [1, -1]
  return $ iterate (addPos (dx, dy)) (dx, dy)
...  -- Other piece types

-- takeUntilIncluding is an inclusive version of takeUntil
takeUntilIncluding :: (a -> Bool) -> [a] -> [a]

possibleMoves board piece = do
  relRay <- rays (pieceType piece)
  let ray = map (addPos src) relRay
  takeUntilIncluding (not . isNothing . pieceAt board)
    (takeWhile notBlocked ray)
  where
    notBlocked pos =
      inBoard pos &&
      all isOtherSide (pieceAt board pos)
    isOtherSide = (/= pieceSide piece) . pieceSide

это использует несколько функций "более высокого порядка":

iterate :: (a -> a) -> a -> [a]
takeUntilIncluding  -- not a standard function
takeWhile :: (a -> Bool) -> [a] -> [a]
all :: (a -> Bool) -> [a] -> Bool
map :: (a -> b) -> [a] -> [b]
(.) :: (b -> c) -> (a -> b) -> a -> c
(>>=) :: Monad m => m a -> (a -> m b) -> m b

(.) - это . оператора, и (>>=) - это do-обозначение "оператор разрыва строки".

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


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

statFuncs :: [ [Double] -> Double ]
statFuncs = [minimum, maximum, mean, median, mode, stddev]

runWith funcs samples = map ($samples) funcs

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


одна вещь, которая забавна, если не особенно практична, это Церковь Цифры. Это способ представления целых чисел используя только функции. Сумасшедшая, я знаю. вот это реализация в JavaScript что я сделал. Это может быть проще понять, чем реализация Lisp/Haskell. (Но, если честно, скорее всего, нет. JavaScript не был предназначен для такого рода вещей.)


было упомянуто, что Javascript поддерживает определенные функции более высокого порядка, включая эссе Джоэла Спольски. Марк Джейсон Доминус написал целую книгу под названием Более Высокий Порядок Perl; источник книги доступен для бесплатной загрузки в различных тонких форматах, включая PDF.

С тех пор, как по крайней мере Perl 3, Perl поддерживает функциональность, более напоминающую Lisp, чем C, но это было не до Perl 5 эта полная поддержка закрытия территорий и всего, что из этого следует, была оказана. И ne из первых реализаций Perl 6 был написан в Haskell, что оказало большое влияние на то, как прогрессировал дизайн этого языка.

примеры подходов функционального программирования в Perl появляются в повседневном программировании, особенно с map и grep:

@ARGV    = map { /\.gz$/ ? "gzip -dc < $_ |" : $_ } @ARGV;

@unempty = grep { defined && length } @many;

С sort также допускает закрытия map/sort/map картина супер общее:

@txtfiles = map { $_->[1] }
            sort { 
                    $b->[0]  <=>     $a->[0]
                              ||
                 lc $a->[1]  cmp  lc $b->[1]
                              ||
                    $b->[1]  cmp     $a->[1]
            }
            map  { -s => $_ } 
            grep { -f && -T }
            glob("/etc/*");

или

@sorted_lines = map { $_->[0] }
                sort {
                     $a->[4] <=> $b->[4] 
                             ||
                    $a->[-1] cmp $b->[-1]
                             ||
                     $a->[3] <=> $b->[3]
                             ||
                     ...
                }
                map { [$_ => reverse split /:/] } @lines;

на reduce функция делает список hackery легко без цикла:

$sum = reduce { $a + $b } @numbers;

$max = reduce { $a > $b ? $a : $b } $MININT, @numbers;

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

try {
   something();
} catch {
   oh_drat();
};

is не встроенный. Это, однако, почти тривиально определено с try будучи функцией, которая принимает два аргумента: закрытие в первом arg и функция, которая принимает закрытие во втором.

Perl 5 не имеет встроенного карринга, хотя для этого есть модуль. Perl 6, однако, имеет карринга и первоклассные продолжения, встроенные прямо в него, плюс намного больше.