Как создать многовариантную функцию haskell?

Мне нужна функция, которая принимает произвольное количество аргументов (все одного типа), делает с ними что-то, а потом возвращает результат. Список аргументов неосуществим в моем конкретном случае.

Когда я просмотрел библиотеки haskell, я увидел, что функция printf (из модуля Text.Printf) использует подобный трюк. К сожалению, я не мог понять эту магию, посмотрев на источник.

Может кто-нибудь объяснить, как достичь этого, или, по крайней мере, на какой-либо веб-странице/документе/где бы я мог найти хорошее описание для этого?

Мотивация:

Причина, в которой я нуждаюсь, очень проста. Для школы (класс компьютерных наук) нам необходимо написать модуль, который способен "записывать" математическое выражение, выражать его как строку (Via, записывая экземпляр Num/Real/etc для собственного типа данных) и выполнять различные операции над ним.

Этот тип данных содержит специальный конструктор для переменной, который может быть заменен значением или любым другим значением определенной функции. Одна из целей - написать функцию, которая принимает такое выражение с некоторым количеством переменных (пары типа (Char,Rational)) и вычисляет результат выражения. Мы должны посмотреть, как лучше выразить цель этой функции. (Моя идея: функция возвращает другую функцию, которая принимает ровно столько аргументов, сколько vars, которые определены в функции - кажется невозможным).

Ответ 1

Ключевыми моментами printf является возможность возврата строки или функции. Скопировано из http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html,

printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

class PrintfType t where
    spr :: String -> [UPrintf] -> t

instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \a -> spr fmts (toUPrintf a : args)

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

variadicFunction :: VariadicReturnClass r => RequiredArgs -> r
variadicFunction reqArgs = variadicImpl reqArgs mempty

class VariadicReturnClass r where
   variadicImpl :: RequiredArgs -> AccumulatingType -> r

instance VariadicReturnClass ActualReturnType where
   variadicImpl reqArgs acc = constructActualResult reqArgs acc

instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where
   variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc)

Например:

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

то мы могли бы использовать

*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0  :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59

Ответ 2

Множество людей рассказывают вам, как создавать вариативные функции, но я думаю, что в этом случае вам действительно лучше использовать список типов [(Char, Rational)].

Ответ 3

В статье wiki по вариационным функциям ссылка на в этой статье. Я полагаю, что это то, что делает printf, но я тоже этого не понимаю. Во всяком случае, это, безусловно, перебор, тем более, что ваши аргументы одного типа. Просто поместите их всех в один список. То, что списки хороши для - суровое число вещей того же типа. Хорошо, это не очень красиво, но это вряд ли будет более уродливым, чем полная поливариадическая функция.

Ответ 4

Я рассмотрел пример, связанный с статьей, на которую ссылается delnan. Посмотрев на это немного, я думаю, что, наконец, понимаю, что происходит:

Он начинается с этого типа:

class BuildList a r  | r-> a where
    build' :: [a] -> a -> r

Этот бит после того, как труба (|) является функциональной зависимостью. В нем говорится, что тип, представленный a, может быть определен типом, представленным r. Другими словами, вам запрещено определять два экземпляра класса BuildList с тем же r (тип возврата), но разные a.

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

> build True :: [Bool]

Так как build просто вызывает build' с пустым списком в качестве первого параметра, это то же самое, что:

> build' [] True :: [Bool]

В этом примере build' явно возвращает список. Из-за функциональной зависимости мы можем связываться только с этим экземпляром класса типа BuildList:

instance BuildList a [a] where
    build' l x = reverse$ x:l

Довольно просто. Второй пример интереснее. Расширяя определение build, он становится:

> build' [] True False :: [Bool]

Какой тип build' в этом случае? Ну, правила приоритета Haskell означают, что вышеупомянутое может быть написано так:

> (build' [] True) False :: [Bool]

Теперь становится ясно, что мы передаем два параметра в build', а затем применяем результат этого выражения к параметру со значением "False". Другими словами, ожидается, что выражение (build' [] True) возвращает функцию типа Bool -> [Bool]. И это связывает нас со вторым экземпляром класса BuildList typeclass:

instance BuildList a r => BuildList a (a->r) where
    build' l x y = build'(x:l) y

В этом вызове l = [] и x = True и y = False, поэтому определение расширяется до build' [True] False :: [Bool]. Эта подпись привязывается к первому экземпляру build', и это довольно очевидно, где оно идет оттуда.

Ответ 5

Ответ KennyTM велик. Ниже приведен пример процесса exec sumOf 1 4 7 10 :: Integer, чтобы дать лучшую иллюстрацию.

sumOf 1 4 7 10
(( \ x -> ( sumOf . (x +) . toInteger ) 1 ) 4 7 10
((sumOf . (1 + ) . toInteger) 4 ) 7 10
( sumOf 5 ) 7 10
( sumOf . (5 + ) . toInteger ) 7 10
sumOf 12 10
sumOf . (12 + ) . toInteger 10
sumof 22
id 22
22