I'm not the biggest fan of varargs, but I always thought both the applicative (f <$> x <*> y
) and idiom ([i| f x y |]
) styles have too many symbols. I usually prefer going the liftA2 f x y
way, but I, too, think that A2 is a little ugly. From this question, I've learned it is possible to implement vararg functions in Haskell. This way, is it possible to use the same principle in order implement a lift function, such that:
lift f a b == pure f <*> a <*> b
I've tried replacing the +
by <*>
on the quoted code:
class Lift r where
lift :: a -> r
instance Lift a where
lift = id
instance (Lift r) => Lift (a -> r) where
lift x y = lift (x <*> y)
But I couldn't manage to get the types right...
I assume you would prefer to use
lift
without type annotations. In this case there are basically two options:First, if we use
OverlappingInstances
, polymorphic functions need annotations:Second, if we instead use
IncoherentInstances
,lift
will work without annotations even on polymorphic functions. However, some complicated stuff still won't check out, for example(lift . lift) (+) (Just (Just 0)) Nothing
.I presented two solutions instead of just the one with
IncoherentInstances
becauseIncoherentInstances
is a rather crude extension that should be avoided if possible. It's probably fine here, but I thought it worthwhile to provide an alternative solution, anyway.In both cases I use the same trick to help inference and reduce annotations: I try to move information from the instance heads to the instance constraints. So instead of
I write
Also, in the other instance I use a plain
b
parameter in the instance head and addb ~ (f a ~ b')
to the constraints.The reason for doing this is that GHC first checks if there is a matching instance head, and it tries to resolve the constraints only after there is a successful match. We want to place the least amount of burden on the instance head, and let the constraint solver sort things out (because it's more flexible, can delay making judgements and can use constraints from other parts of the program).
Notice that you can chain any number of
<*>
, to get a function of the formIf we have the type
a0 -> .. -> an
andf a0 -> .. -> f an
, we can computef
from this. We can encode this relation, and the most general type, as followsAs you may expect, the "recursive case" instance will simply apply
<*>
once, then recurse:The base case is when there is no more function. Since you can't actually assert "
a
is not a function type", this relies on overlapping instances:Because of GHCs instance selection rules, the recursive case will always be selected, if possible.
Then the function you want is
lift' . pure
:This is where the functional dependency on
Lift
becomes very important. Sincef
is mentioned only in the context, this function would be ill-typed unless we can determine whatf
is knowing onlya
andb
(which do appear in the right hand side of=>
).This requires several extensions:
and, as usual with variadic functions in Haskell, normally the only way to select an instance is to give an explicit type signature.
The way I have written it, GHC will happily accept
lift
which is polymorphic in the arguments tof
(but notf
itself).Sometimes the context is sufficient to infer the correct type. Note that the argument type is again polymorphic.
As of GHC >= 7.10,
OverlappingInstances
has been deprecated and the compiler will issue a warning. It will likely be removed in some later version. This can be fixed by removingOverlappingInstances
from the{-# LANGUAGE .. #-}
pragma and changing the 2nd instance to