Actually, you only need Functor
:
listUncurry :: Functor f => (a -> a -> a -> r) -> f [a] -> f r
listUncurry h p =
(\[x, y, z] -> h x y z) <$> p
To me, a hint that only Functor
is necessary is when you have a code pattern like:
do x <- m
return (f ...)
This is equivalent to
m >>= (\x -> return (f ...))
which is the same as
fmap (\x -> f ...) m
This is because the monad laws imply this identity:
fmap f xs = xs >>= return . f
Polyvariadic listUncurry
I don't really recommend this in most circumstances since it turns what would be compile time errors into runtime errors, but this is how you could implement a polyvariadic 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"
You will need a lot of explicit type annotations if you use it too. There is probably a way to use a type family or functional dependency to help with that, but I can't think of it off the top of my head at the moment. Since that is probably solvable (to an extent at least), in my mind the bigger problem is the type errors being changed from compile time errors to runtime errors.
Sample usage:
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
A safer listUncurry
If you change the class to
class ListUncurry a x r where
listUncurry :: a -> [x] -> Maybe r
and change the error cases in the instances appropriately, you will at least get a better interface to handling the errors. You could also replace the Maybe
with a type that differentiates between the "too many" and "too few" argument errors if you wanted to retain that information.
I feel that this would be a bit better of an approach, although you will need to add a bit more error handling (Maybe
's Functor
, Applicative
and Monad
interfaces will make this fairly nice though).
Comparing the two approaches
It ultimately depends on what sort of error this would represent. If the program execution can no longer continue in any meaningful way if it runs into such an error, then the first approach (or something like it) might be more appropriate than the second. If there is any way to recover from the error, the second approach would be better than the first.
Whether or not a polyvariadic technique should be used in the first place is a different question. It might be better to restructure the program to avoid the additional complexity of the polyvariadic stuff.