Когда мне следует писать свои функции в карри?
Недавно я начал изучать F # и сталкивался с карьерами для простых примеров, таких как:
Рассмотрим функцию, которая вычисляет продажи, умножая цену p
на количество проданных единиц n
.
let sales (p,n) = p * (float n);;
Тип этой функции задается как
val sales : p:float * n:int -> float
то есть. возьмите пару float
и int
и верните a float
.
Вместо этого мы можем написать это как карриную функцию
let salesHi p n = p * (float n);;
Тип этой функции задается как
val salesHi : p:float -> n:int -> float
то есть. принимает значение float
и возвращает функцию от int
до float
.
В простых случаях это, по-видимому, не имеет значения
sales (0.99, 100);;
salesHi 0.99 100;;
Оба дают
val it : float = 99.0
Однако с помощью функции curried я могу указать цену для определенных элементов для получения новых функций. Например.
let salesBeer = salesHi 5.99;;
let salesWine = salesHi 14.99;;
Тогда salesBeer 2
дает 11.98
и salesWine 2
дает 29.98
.
Кроме того, я заметил, что встроенные операторы, такие как +
, определяются как функции, поэтому я могу написать, например:
let plus2 = (+) 2;
List.map plus2 [1;3;-1];;
и получим
val it : int list = [3; 5; 1]
Это похоже на хорошую вещь. Поэтому, когда я хочу реализовать функцию на императивном языке, который бы принял аргументы n > 1
, должен ли я, например, всегда использовать функцию curries в F # (если аргументы независимы)? Или я должен использовать простой маршрут и использовать регулярную функцию с помощью n
-tuple и curry позже, если это необходимо? Или что-то еще?
Как программисты F # решают, когда делать функцию в карри или использовать регулярную функцию с кортежем?
Ответы
Ответ 1
Когда вы выбираете между картой и корневой формой, главное, чтобы рассмотреть, является ли кортеж, который вы принимаете в качестве аргумента, означает что-либо.
Заполненная форма. Например, float * float
может представлять диапазон, и тогда рекомендуется использовать заполненную форму.
let normalizeRange (lo, hi) = if hi < lo then (hi, lo) else (lo, hi)
let expandRange by (lo, hi) = (lo - by, hi + by)
Хорошо, что вы можете создавать функции, которые работают на диапазонах. Например, вы можете написать что-то вроде:
randomRange() |> normalizeRange |> expandRange 10
Curried form.. С другой стороны, карри-форма - лучший выбор, если кортеж всех аргументов не является автономным значением с некоторым полезным значением. Например, силовая функция pown 2.0 10
- два аргумента - это количество и мощность, но маловероятно, что вы когда-либо использовали кортеж (2.0, 10)
где-то еще в своей программе.
Карридная форма также полезна, когда у вас есть один "более важный" аргумент, потому что тогда вы можете использовать конвейерную обработку. Например, List.map
должна быть указана так, чтобы это можно было сделать:
[1 .. 10] |> List.map (fun n -> n + 1)
Ответ 2
Или я должен взять простой маршрут и использовать регулярную функцию с n-кортежем и карри позже, если необходимо?
Чем проще найти работу? пусть материал x y z =... не просто меньше печатает, чем позволяет материалу (x, y, z), это на самом деле меньше работы, выполняемой языком. Вторая версия должна распределять кортеж, а затем разрушать кортеж в аргументы против первой версии, которая просто потребляет аргументы.
Карьерная форма - это идиоматический способ записи функций в F #. Я действительно не могу придумать веские основания использовать форму кортежа, если данные уже не были сохранены в виде кортежа.
Ответ 3
Еще одно соображение - если вы планируете взаимодействовать с С# или VB.NET, не беспокойтесь о валютной форме, так как они не очень хорошо знакомы с этими языками. С другой стороны, заполненная форма отображается как нормальный набор аргументов с точки зрения С#/VB.NET и очень естественна для потребления.
Ответ 4
Форма кортежа на самом деле немного опасна. Он может быть похож на языки в стиле ALGOL (= 90% популярных языков), но он работает по-другому.
Рассмотрим это:
foo bar(x,y)
Во всех языках в стиле ALGOL, которые допускают этот синтаксис, это означает "вызов bar
с x
и y
в качестве аргументов и передать результаты в foo
" - когда foo
не имеет быть методом (это может быть синтаксис, например print
в 2. * Python).
Однако в языках типа Lambda (например, ML, Haskell и F #) это означает "вызов foo
с bar
в качестве аргумента, а затем вызов результата (который является функцией) с кортежем (x,y)
как аргумент.
Если вы новичок в языках в стиле лямбда, это может быть довольно запутанным. Теперь, если вы использовали форму карри, эквивалент был бы следующим:
foo bar x y
что так же неправильно, как foo bar(x,y)
- но не так запутанно! Даже программисты, не знакомые с языками в стиле лямбда, могут легко понять, что bar
не является первой функцией, которая будет вызываться в foo bar x y
. Ошибка сразу понятна.