Как вы определяете 2-й (или 3-й, 4-й, ...) параметр в F # или любом другом функциональном языке? - PullRequest
13 голосов
/ 16 декабря 2009

Я только начинаю с F # и вижу, как вы можете использовать карри для предварительной загрузки 1-го параметра в функцию. Но как сделать это с помощью второго, третьего или любого другого параметра? Будут ли названные параметры сделать это проще? Существуют ли другие функциональные языки с именованными параметрами или каким-либо другим способом сделать карри безразличным к порядку параметров?

Ответы [ 5 ]

19 голосов
/ 16 декабря 2009

Обычно вы просто используете лямбду:

fun x y z -> f x y 42

- это функция, подобная 'f', но с третьим параметром, связанным с 42.

Вы также можете использовать комбинаторы (как кто-то упомянул «переворот» Хаскелла в комментарии), которые переупорядочивают аргументы, но я иногда нахожу это непонятным.

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

F # имеет именованные параметры для методов (не для значений функций с привязкой по буквам), но имена применяются к «кортежным» параметрам. Именованные параметры карри не имеют особого смысла; если бы у меня была функция карри с двумя аргументами 'f', я бы ожидал, что с учетом

let g = f
let h x y = f x y

тогда «g» или «h» могли бы заменить «f», но «именованные» параметры делают это не обязательно верным. Другими словами, «именованные параметры» могут плохо взаимодействовать с другими аспектами языкового дизайна, и лично я не знаю, насколько удачен хороший дизайн для «именованных параметров», который хорошо взаимодействует с «значениями функций карри первого класса».

6 голосов
/ 05 ноября 2014

Просто для полноты - и поскольку вы спрашивали о других функциональных языках - именно так вы бы поступили в OCaml, возможно, «матери» F #:

$ ocaml
# let foo ~x ~y = x - y ;;
val foo : x:int -> y:int -> int = <fun>
# foo 5 3;;
- : int = 2
# let bar = foo ~y:3;;
val bar : x:int -> int = <fun>
# bar 5;;
- : int = 2

Таким образом, в OCaml вы можете жестко закодировать любой именованный параметр, просто используя его имя (y в приведенном выше примере).

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

Я в меньшинстве и использую их - но это действительно что-то, что легко эмулируется в F # с привязкой локальной функции:

let foo x y = x - y
let bar x = foo x 3
bar ...
5 голосов
/ 16 декабря 2009

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

Вы можете попробовать создать что-то вроде функции flip на Haskell. Создание вариантов, которые перемещают аргумент дальше в списке аргументов, не должно быть слишком сложным.

let flip f a b = f b a
let flip2 f a b c = f b c a
let flip3 f a b c d = f b c d a
4 голосов
/ 16 декабря 2009

В Python вы можете использовать functools.partial или лямбду. Python назвал аргументы. functools.partial может использоваться для указания первых позиционных аргументов, а также любого именованного аргумента.

from functools import partial

def foo(a, b, bar=None):
    ...

f = partial(foo, bar='wzzz') # f(1, 2) ~ foo(1, 2, bar='wzzz')
f2 = partial(foo, 3)         # f2(5) ~ foo(3, 5)

f3 = lambda a: foo(a, 7)     # f3(9) ~ foo(9, 7)
3 голосов
/ 22 июня 2018

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

Я считаю, что чаще всего это нужно для частичного применения деления или вычитания.

> let halve = (/) >> (|>) 2.0;;
> let halfPi = halve System.Math.PI;;

val halve : (float -> float)
val halfPi : float = 1.570796327

Обобщая, мы можем объявить функцию applySecond:

> let applySecond f arg2 = f >> (|>) arg2;;
val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)

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

> let applySecond f arg2 =
-     let ff = (|>) arg2
-     f >> ff;;
val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)

Теперь f - это функция от 'a до 'b -> 'c. Он состоит из ff, функции от 'b -> 'c до 'c, которая является результатом частичного применения arg2 к оператору прямого конвейера. Эта функция применяет конкретное значение 'b, переданное для arg2 к своему аргументу. Поэтому, когда мы сочиняем f с ff, мы получаем функцию от 'a до 'c, которая использует заданное значение для аргумента 'b, и это именно то, что мы хотели.

Сравните первый пример выше со следующим:

> let halve f = f / 2.0;;
> let halfPi = halve System.Math.PI;;

val halve : f:float -> float
val halfPi : float = 1.570796327

Также сравните их:

let filterTwoDigitInts = List.filter >> (|>) [10 .. 99]
let oddTwoDigitInts = filterTwoDigitInts ((&&&) 1 >> (=) 1)
let evenTwoDigitInts = filterTwoDigitInts ((&&&) 1 >> (=) 0)

let filterTwoDigitInts f = List.filter f [10 .. 99]
let oddTwoDigitInts = filterTwoDigitInts (fun i -> i &&& 1 = 1)
let evenTwoDigitInts = filterTwoDigitInts (fun i -> i &&& 1 = 0)

В качестве альтернативы, сравните:

let someFloats = [0.0 .. 10.0]
let theFloatsDividedByFour1 = someFloats |> List.map ((/) >> (|>) 4.0)
let theFloatsDividedByFour2 = someFloats |> List.map (fun f -> f / 4.0)

Кажется, что лямбда-версии легче читать.

...