Подтвердить что ты не робот

Практическое использование карриных функций?

Есть тонны учебников о том, как выполнять функции curry, и как много вопросов здесь, в stackoverflow. Однако после прочтения The Little Schemer, нескольких книг, учебников, сообщений в блогах и потоков stackoverflow я до сих пор не знаю ответа на простой вопрос: "Какой смысл карри?" Я действительно понимаю, как выполнять функцию, а не "почему?". за ним.

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

изменить: Принимая во внимание некоторые примеры из TLS, какое преимущество

(define (action kind)
    (lambda (a b)
        (kind a b)))

в отличие от

(define (action kind a b)
    (kind a b))

Я могу видеть только больше кода и без дополнительной гибкости...

4b9b3361

Ответ 1

Одним эффективным использованием карри-функций является уменьшение количества кода.

Рассмотрим три функции, две из которых почти идентичны:

(define (add a b)
  (action + a b))

(define (mul a b)
  (action * a b))

(define (action kind a b)
  (kind a b))

Если ваш код вызывает add, он, в свою очередь, вызывает action с видом +. То же самое с mul.

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

Все add и sum do, завершают вызов action с помощью соответствующего kind. Теперь рассмотрим кардинальные определения этих функций:

(define add-curried
  ((curry action) +))

(define mul-curried
  ((curry action) *))

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

Этот подход позволяет вам писать меньше кода с высоким уровнем ремонтопригодности.

Представьте себе, что функция action будет немедленно переписана, чтобы принять еще 3 аргумента. Без currying вам придется переписать свои реализации add и mul:

(define (action kind a b c d e)
  (kind a b c d e))

(define (add a b c d e)
  (action + a b c d e))

(define (mul a b c d e)
  (action * a b c d e))

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

Ответ 2

Они могут сделать код более удобным для чтения. Рассмотрим следующие два фрагмента Haskell:

lengths :: [[a]] -> [Int]
lengths xs = map length xs

lengths' :: [[a]] -> [Int]
lengths' = map length

Зачем указывать имя переменной, которую вы не собираетесь использовать?

Выполненные функции также помогают в таких ситуациях:

doubleAndSum ys = map (\xs -> sum (map (*2) xs) ys

doubleAndSum' = map (sum . map (*2))

Удаление лишних переменных делает код более удобным для чтения, и нет необходимости в том, чтобы вы мысленно уяснили, что такое xs и что такое ys.

НТН.

Ответ 3

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

Ответ 4

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

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

(Другие уже рассмотрели некоторые практические последствия этого решения, поэтому я остановлюсь здесь.)

Ответ 5

Используя all :: (a -> Bool) -> [a] -> Bool с предикатом в карри.

all (`elem` [1,2,3]) [0,3,4,5]

Операторы инфикса Haskell могут быть расположены с обеих сторон, поэтому вы можете легко заархивировать иглу или сторону контейнера функции elem (is-element-of).

Ответ 6

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

Ответ 7

Таким образом, вам не нужно увеличивать шаблон с небольшим количеством лямбда.

Ответ 8

Очень легко создавать замыкания. Время от времени я использую SRFI-26. Это действительно мило.

Ответ 9

В самом себе каррирование - синтаксический сахар. Синтаксический сахар - это то, что вы хотите сделать легко. C, например, хочет сделать инструкции, которые являются "дешевыми" на ассемблере, например, инкрементными, легкими и поэтому у них есть синтаксический сахар для инкрементации, обозначение ++.

 t = x + y
 x = x + 1

заменяется на t = x ++ + y

Функциональные языки могут так же легко иметь такие вещи.

f(x,y,z) = abc 
g(r,s)(z) = f(r,s,z). 
h(r)(s)(z) = f(r,s,z)

но вместо этого все автоматическое. И это позволяет передавать g, привязанные определенным r0, s0 (т.е. конкретными значениями), которые передаются как одна переменная.

Возьмем, например, функцию сортировки perl, которая принимает сортировать список где sub - функция двух переменных, которая вычисляется как логическая и list - это произвольный список.

Вы, естественно, хотели бы использовать операторы сравнения (< = > ) в Perl и иметь sortordinal = sort (< = > ) где сортировка осуществляется по спискам. Чтобы сделать это, вы бы выбрали функцию карри. А на самом деле вид списка определяется именно таким образом в Perl.

Короче: каррирование - это сахар, чтобы сделать функции первого класса более естественными.

Ответ 10

Я хотел бы добавить пример в ответ @Francesco.

enter image description here