На самом деле вам нужно всего Functor
:
listUncurry :: Functor f => (a -> a -> a -> r) -> f [a] -> f r
listUncurry h p =
(\[x, y, z] -> h x y z) <$> p
Для меня намек на то, что нужен только Functor
, - это когда у вас есть такой шаблон кода, как:
do x <- m
return (f ...)
Это эквивалентно
m >>= (\x -> return (f ...))
который совпадает с
fmap (\x -> f ...) m
Это потому, что законы монад подразумевают эту идентичность:
fmap f xs = xs >>= return . f
Поливариадический listUncurry
Я не рекомендую это делать в большинстве случаев, так как это превращает ошибки времени компиляции в ошибки времени выполнения, но вот как можно реализовать поливариадический listUncurry
:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
class ListUncurry a x r where
listUncurry :: a -> [x] -> r
instance ListUncurry k a r => ListUncurry (a -> k) a r where
listUncurry f (x:xs) = listUncurry (f x) xs
listUncurry _ _ = error "listUncurry: Too few arguments given"
instance ListUncurry r a r where
listUncurry r [] = r
listUncurry _ _ = error "listUncurry: Too many arguments given"
Вам понадобится много явных аннотаций типов, если вы их тоже используете. Вероятно, есть способ использовать семейство типов или функциональную зависимость, чтобы помочь с этим, но я не могу придумать это в настоящий момент. Поскольку это, вероятно, разрешимо (по крайней мере, в какой-то степени), на мой взгляд, более серьезной проблемой является изменение типа ошибок с ошибок времени компиляции на ошибки времени выполнения.
Пример использования:
ghci> listUncurry ord ['a'] :: Int
97
ghci> listUncurry ((==) :: Int -> Int -> Bool) [1,5::Int] :: Bool
False
ghci> listUncurry ((==) :: Char -> Char -> Bool) ['a'] :: Bool
*** Exception: listUncurry: Too few arguments given
ghci> listUncurry ((==) :: Char -> Char -> Bool) ['a','b','c'] :: Bool
*** Exception: listUncurry: Too many arguments given
Более безопасный listUncurry
Если вы измените класс на
class ListUncurry a x r where
listUncurry :: a -> [x] -> Maybe r
и соответствующим образом измените случаи ошибок в экземплярах, вы, по крайней мере, получите лучший интерфейс для обработки ошибок. Вы также можете заменить Maybe
типом, который различает ошибки аргументов «слишком много» и «слишком мало», если вы хотите сохранить эту информацию.
Я считаю, что это было бы немного лучше, хотя вам нужно будет добавить немного больше обработки ошибок (хотя интерфейсы Maybe
, Functor
, Applicative
и Monad
сделают это довольно приятным).
Сравнение двух подходов
В конечном итоге это зависит от того, какую ошибку это представляет. Если выполнение программы больше не может продолжаться каким-либо значимым образом при возникновении такой ошибки, то первый подход (или что-то подобное) может быть более подходящим, чем второй. Если существует какой-либо способ исправить ошибку, второй подход будет лучше, чем первый.
Другой вопрос, следует ли вообще использовать поливариадную технику. Возможно, было бы лучше реструктурировать программу, чтобы избежать дополнительной сложности поливариадного материала.
person
David Young
schedule
12.09.2015
listUncurry
использует монадическийdo
, но для этого нет причин - я хочу исправить и это, и поливариадность :) - person user1441998   schedule 12.09.2015do
может быть таким же простым, какfmap (\[x1,x2,x3] -> h x1 x2 x3) p
, как в ответе Дэвида Янга. - person duplode   schedule 12.09.2015