Есть ли в Haskell функции / кортежи с переменными параметрами? - PullRequest
25 голосов
/ 28 августа 2011

Функция uncurry работает только для функций, принимающих два аргумента:

uncurry :: (a -> b -> c) -> (a, b) -> c

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

uncurry2 f (a, b)          = f a b
uncurry3 f (a, b, c)       = f a b c
uncurry4 f (a, b, c, d)    = f a b c d
uncurry5 f (a, b, c, d, e) = f a b c d e

Но это быстро становится утомительным.Есть ли способ обобщить это, поэтому мне нужно написать только одну функцию?

Ответы [ 3 ]

23 голосов
/ 28 августа 2011

Попробуйте uncurryN из набора кортеж . Как и все формы перегрузки, это реализовано с использованием классов типов. В этом случае вручную прописывается количество экземпляров до 15, что должно быть более чем достаточно.

Функции Variadic также возможны с использованием классов типов. Одним из примеров этого является Text.Printf . В этом случае это делается структурной индукцией по типу функции. Упрощенно, это работает так:

class Foo t

instance Foo (IO a)
instance Foo b => Foo (a -> b)

foo :: Foo

Нетрудно понять, что foo может быть создан для типов IO a, a -> IO b, a -> b -> IO c и так далее. QuickCheck также использует эту технику.

Структурная индукция не будет работать на кортежах, так как кортеж n полностью не связан с кортежем n + 1 , поэтому экземпляры должны быть прописано вручную.

11 голосов
/ 28 августа 2011

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

Поэтому любой жизнеспособный подход для работы с кортежами потребует генерации кода - либо с использованием TH, либо внешнего инструмента, как в пакете tuple.

Чтобы подделать его без использования сгенерированного кода, вы должны сначала прибегнуть к использованию рекурсивных определений - обычно это пары с правым вложением со значением «nil» для обозначения конца, либо (,) и () или что-то эквивалентное их. Вы можете заметить, что это похоже на определение списков в терминах (:) и [] - и на самом деле рекурсивно определенные искусственные кортежи такого рода можно рассматривать как структуры данных на уровне типа (список типы) или как разнородные списки (например, HList работает таким образом).

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

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

2 голосов
/ 28 августа 2011

Не существует простого способа написать единственное определение uncurry, которое будет работать для различного числа аргументов.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...