Мне нужна функция, которая принимает произвольное количество аргументов (все одного типа), что-то с ними делает и впоследствии возвращает результат. Список аргументов неосуществим в моем конкретном случае.
Когда я просматривал библиотеки haskell, я увидел, что функция printf
(из модуля Text.Printf
) использует аналогичный обмануть. К сожалению, я не мог понять эту магию, посмотрев на источник.
Может кто-нибудь объяснить, как этого добиться, или, по крайней мере, какую-нибудь веб-страницу / бумагу / где-нибудь, где я мог бы найти хорошее описание для этого?
Мотивация:
Причина, по которой мне это нужно, действительно довольно проста. Для школы (урок информатики) мы должны написать модуль, который может «записывать» математическое выражение, выражать его в виде строки (посредством записи экземпляра Num / Real / и т. Д. Для собственного типа данных) и выполнять различные операции над ним.
Этот тип данных содержит специальный конструктор для переменной, который может быть заменен значением или чем-то еще указанной функцией. Одна из целей - написать функцию, которая принимает такое выражение с некоторым количеством переменных (пары типа (Char, Rational)
) и вычисляет результат выражения. Мы должны посмотреть, как лучше выразить цель функции. (Моя идея: Функция возвращает другую функцию, которая принимает ровно столько аргументов, сколько переменных, определенных в функции, - кажется невозможной).
Ключевыми моментами 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
Я просмотрел пример , ссылка на который содержится в статье, на которую ссылается Делнан. Посмотрев на него немного, я думаю, что наконец понял, что происходит:
Он начинается с этого типа class:
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
:
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 '
, и довольно очевидно, куда она оттуда идет.
В вики-статье о вариативных функциях есть ссылка на эту статью . Полагаю, это то, что делает printf, но я тоже этого не понимаю. В любом случае, это определенно излишество, тем более что все ваши аргументы одного типа. Просто поместите их все в один список. Вот для чего хороши списки - произвольное количество однотипных вещей. Прекрасно, не очень красиво, но вряд ли будет уродливее полной поливариадной функции.
Многие люди говорят вам, как создавать вариативные функции, но я думаю, что в этом случае вам на самом деле лучше просто использовать список типа [(Char, Rational)].