Подтвердить что ты не робот

Имеет ли Haskell вариационные функции/кортежи?

Функция 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

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

4b9b3361

Ответ 1

Попробуйте uncurryN из пакета tuple. Как и все формы перегрузки, он реализуется с использованием классов типов. В этом случае вручную указывая экземпляры до 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-кортежем, так что поэтому экземпляры должны быть написаны вручную.

Ответ 2

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

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

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

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

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

Ответ 3

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

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