Частичное применение в haskell с несколькими аргументами

Учитывая некоторую функцию f(x1,x2,x3,..,xN), часто полезно применять ее частично в нескольких местах. Например, для N=3 мы можем определить g(x)=f(1,x,3). Однако стандартное частичное применение в Haskell не работает таким образом и позволяет нам лишь частично применить функцию, зафиксировав ее первые аргументы (потому что все функции на самом деле принимают только один аргумент). Есть ли простой способ сделать что-то вроде этого:

g = f _ 2 _
g 1 3

с выходным значением f 1 2 3? Конечно, мы могли бы сделать лямбда-функцию

g=(\x1 x3 -> f x1 2 x3)

но я нахожу это совершенно нечитаемым. Например, в Mathematica это работает так, что я нахожу довольно приятным:

g=f[#1,2,#2]&
g[1,3]

с выходом f[1,2,3],

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

h = g. f _ 2 . k

получить h 3 = g(f(k(3),2)),

5 ответов

Вы можете прочитать этот вопрос о том, как изменить порядок аргументов, а затем использовать частичное применение, но на самом деле самый чистый и ясный способ сделать это в настоящее время в Haskell - это просто напрямую:

g x y = f x 2 y

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

g . (`f` 2) . k

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

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

Другие вещи для рассмотрения:

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

fix_2 f a = \x -> f x a
fix1_3 f a b = \x -> f a x b

h = g . fix_2 f 2 . k

Не так хорошо, как ваш гипотетический "пустой" синтаксис, но все в порядке; ты можешь читать fix_2 в качестве тега, идентифицирующего частичную схему применения в использовании1.

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

Как более сложная идея, я считаю, что вы должны быть в состоянии написать квазикоттер Template Haskell, который фактически реализует ваш псевдосинтаксис "подчеркивания - это параметры подразумеваемой функции". Это позволит вам писать выражения, лежащие так:

h = g . [partial| f _ |] . k

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

h = g . [partial| f 1 _ 3 4 _ 6 |] . k

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


1 Обратите внимание, что исправление только второго аргумента уже имеет стандартную вспомогательную функцию: flip, Частичное обращение ко второму аргументу может показаться не похожим на замену первых двух аргументов, но благодаря ленивой оценке и карри они на самом деле одно и то же!

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

Самый простой (и канонический) способ - определить лямбду. Это гораздо более читабельно, если вы используете значимые имена аргументов, где это возможно

getCurrencyData :: Date -> Date -> Currency -> IO CurrencyData
getCurrencyData fromDate toDate ccy = {- implementation goes here -}

вы можете определить новую функцию с помощью лямбда-синтаксиса

getGBPData = \from to -> getCurrencyData from to GBP

или без него

getGBPData from to = getCurrencyData from to GBP

или вы можете использовать комбинаторы, но я думаю, что это довольно некрасиво

getGBPData = \from to -> getCurrencyData from to GBP
           = \from to -> flip (getCurrencyData from) GBP to
           = \from    -> flip (getCurrencyData from) GBP
           = \from    -> (flip . getCurrencyData) from GBP
           = \from    -> flip (flip . getCurrencyData) GBP from
           =             flip (flip . getCurrencyData) GBP
Другие вопросы по тегам