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