Обратное каррирование?

Я хотел бы составить функции определенным образом. Пожалуйста, рассмотрите эти 2 функции в псевдокоде (не F#)

F1 = x + y
F2 = F1 * 10 // note I did not specify arguments for F1, 'reverse curry' for lack of a better word

Я бы хотел, чтобы F# понял, что, поскольку

let F1 x y = x + y
//val F1 : int -> int -> int

код let F2 = F1 * 10 дал бы мне ту же подпись, что и F1: val F2 : int -> int -> int, а вызов F2 2 3 привел бы к 50: (2 + 3) * 10. Это было бы довольно умно...

То, что происходит, совсем другое. Первая строка идет, как и ожидалось:

let F1 x y = x + y
//val F1 : int -> int -> int

но когда я добавляю вторую строку let F2 = F1 * 10, она сбрасывает F#. Он жалуется, что the type int does not match the type 'a -> 'b -> 'c и что F1 теперь requires member ( + ).

Я мог бы, конечно, написать так:

let F1(x, y) = x + y
let F2(x, y) = F1(x, y) * 10

Но теперь я мог бы также использовать C#, мы уже не так далеко. Аргументы с кортежами во многом нарушают элегантность F#. Кроме того, мои настоящие функции F1 и F2 имеют гораздо больше аргументов, чем просто 2, так что это заставляет меня косить глазами, а именно то, чего я хотел избежать, используя F#. Гораздо естественнее было бы сказать так:

let F1 x y = x + y
let F2 = F1 * 10

Можно ли (почти) это сделать?

Для дополнительных кредитов: что именно происходит с этими сообщениями об ошибках? Почему вторая строка let F2 = F1 * 10 меняет набор текста на первую?

Заранее спасибо за ваши мысли,

Герт-Ян

update Два подхода, которые (почти) делают то, что описано.

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

let F1 (a, b) = a + b
let F2 = F1 >> (*) 10

F2(2, 3) // returns 50

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

type Arg (a, b) =
    member this.A = a
    member this.B = b

let F1 (a:Arg) = a.A + a.B
let F2 (a:Arg) = F1(a) * 10

F2 (Arg(2, 3)) // returns 50

person gjvdkamp    schedule 04.05.2011    source источник
comment
Взгляните на этот недавний вопрос о композиции в Хаскелл. По сути, это та же проблема, но есть несколько интересных решений, которые можно было бы реализовать на F# (я не носитель языка).   -  person hammar    schedule 04.05.2011
comment
привет, Хаммар, спасибо, что Haskell заставляет меня чувствовать себя дислексиком. Я думаю, это то, что они имеют в виду под «супом со сгущенкой». Я попробую позже, может быть, весь Haskell выглядит как хороший «тренажерный зал для ума». Спасибо!   -  person gjvdkamp    schedule 04.05.2011
comment
Если F1 принимает кортеж, то F2 можно записать как let F2 = F1 >> (*) 10   -  person ildjarn    schedule 04.05.2011
comment
@ildjarn спасибо за пример, сначала я не понял подход с кортежами. Я также обновил ОП с таким подходом.   -  person gjvdkamp    schedule 05.05.2011


Ответы (3)


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

Если вы часто используете этот конкретный шаблон, вы можете определить оператор для умножения функции (с двумя параметрами) на скаляр:

let ( ** ) f x = fun a b -> (f a b) * x

let F1 x y = x + y
let F2 = F1 ** 10

К сожалению, вы не можете добавить реализацию стандартных числовых операторов (* и т.д.) к существующим типам (таким как 'a -> 'b -> int). Впрочем, это довольно частый запрос (и для других целей он бы пригодился). В качестве альтернативы вы можете обернуть функцию в какой-нибудь объект, предоставляющий перегруженные числовые операторы (и содержащий некоторый Invoke метод для запуска функции).

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

Чтобы объяснить сообщение об ошибке - он жалуется на выражение F1 * 10:

ошибка FS0001: Тип 'int' не соответствует типу ''a -> 'b -> 'c'

Я думаю, это означает, что компилятор пытается найти экземпляр для оператора *. С правой стороны он определяет, что это должно быть int, поэтому он думает, что левая часть также должна быть int, но на самом деле это функция двух аргументов - что-то вроде 'a -> 'b -> c'.

person Tomas Petricek    schedule 04.05.2011
comment
Привет, поэтому я думаю, что короткий ответ - нет, у вас не может быть функции HO, которая может умножать любую функцию на любой скаляр. Поскольку F# статически типизирован с помощью дженериков под капотом, ему нужно знать количество аргументов для указанной функции? Это правильно подводит итог? Тогда нет возможности оставить количество аргументов открытым на данный момент, и обходным путем становится создание этой функции HO в качестве оператора, который выполняет uncurry и умножение за один раз, но это будет специфично для функции с 2 аргументами? - person gjvdkamp; 04.05.2011
comment
Да, это правильно. Единственный способ оставить количество параметров открытым для функции — использовать некоторые неприятные приемы отражения (но это не совсем дает вам то, что вы хотите). Однако вы можете передавать параметры в кортеже (используя функции с одним параметром). - person Tomas Petricek; 04.05.2011
comment
Хм... думаю, тогда я бы пошел на запись, просто чтобы я мог назвать аргументы, чтобы избежать возможности путаницы. На самом деле это был бы прекрасный дизайн, и тогда мне нужно было бы передать только одну запись. Было бы здорово, если бы была языковая функция (как описано) для поддержки этого, но я думаю, что запись будет работать нормально. - person gjvdkamp; 05.05.2011

Это было бы довольно умно...

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

Можно ли (почти) это сделать?

Я не говорю на F#, но в Haskell вы должны удалить F1, затем составить с помощью *10, а затем выполнить карри:

f2 = curry ((*10) . uncurry f1)

Что на диалекте ML, таком как F #, выглядит примерно так:

let curry f x y = f (x,y)
let uncurry f (x,y) = f x y

let mult x y = x * y

let F1 x y = x + y
let F2 = curry (uncurry F1 >> mult 10)

(Я не был уверен, есть ли curry и uncurry в стандартной библиотеке F#, поэтому я определил их. Также может быть более красивый способ частичного применения * без определения mult.)

person Fred Foo    schedule 04.05.2011
comment
'умный' LOL Я думаю, да. Также часто, когда я описываю, что я действительно хотел бы сделать, APL или Haskell, как правило, всплывают. Так много красивых языков, так мало времени... Позвольте мне попытаться обдумать то, что вы с Томасом сказали, и вернуться сюда. - person gjvdkamp; 04.05.2011
comment
Мне нравится абстракция этого подхода, когда функция каррируется и не каррируется, хотя они тоже специфичны для двух аргументов. Не нашли для этого ни одного универсального оператора в F #, Haskell может сделать это для любой функции? Это мило. Тем не менее, этот метод вносит некоторый «технический шум», когда много кода описывает «как», а не просто «что». В идеале я хотел бы получить код, который читается так же, как если бы вы написали его на доске для не-программы, но я думаю, вы правы в том, что синтаксический анализатор/система типов трудно работать для всех различных случаев. - person gjvdkamp; 04.05.2011
comment
@gjvdkamp: некоторая магия классов типов может помочь в Haskell. Я не знаю, поддерживает ли F# классы типов. - person Fred Foo; 04.05.2011
comment
Я должен изучить это. Основная причина, по которой я переключился на F#, заключалась в том, чтобы иметь возможность писать код более «интуитивно» без всех фигурных скобок, операторов возврата, изменяемых переменных и т. д., которые есть в C#. Таким образом, хотя F# и Haskell могут предоставить инструменты для выполнения того, что я описал, они введут много вещей, которые выглядят как «магические заклинания». Это помешало бы моей главной цели перехода на F#. Спасибо за ваше время! - person gjvdkamp; 05.05.2011

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

let F1 = (+)
let F2 = (<<)((*)10) << F1
person Ed'ka    schedule 06.05.2011