Reputation: 23
I have a function:
sum f l1 l2 = (f l1) + (f l2)
How to correct this function to be working when called with different types of lists? eg:
sum length [1,2] ['a','b']
Upvotes: 2
Views: 420
Reputation: 23135
There's no general way to do this at the moment unfortunately. You could try like this previous answer suggested as follows:
sum' :: Num b => (forall a. [a] -> b) -> [c] -> [d] -> b
sum' f l1 l2 = f l1 + f l2
And whilst this works with length
, it doesn't really work with much else.
The issue is that the type signature in this answer Num b => forall a. [a] -> b
. That means your function must work for all types of lists, and the only sensible function from Num b => forall a. [a] -> b
is length
. If you think there's another feel free to give me an example, but I suspect all the other examples are either variations of length or silly ones that return a constant.
And if length
is the only sensible argument for sum'
, then it's silly to define sum'
, you might as well define sumLength
like follows
sumLength :: Num b => [c] -> [d] -> b
sumLength l1 l2 = genericLength l1 + genericLength l2
Indeed, lets define the following:
g :: Enum a => [a] -> Int
g = (foldl' 0 (+)) . (map fromEnum)
This is a weird probably useless function, but it does something non-trivial. It converts all the values to their Enum
int
representation and sums them and spits out an Integer.
So sum' g l1 l2
should work, but it doesn't. To get this to work, you'd have to define a new function:
sum'' :: Enum c, Enum d => (Enum a => forall a. [a]) -> [c] -> [d] -> Int
sum'' f l1 l2 = f l1 + f l2
And indeed, too use any function with different constraints, you'll have to define a new version of sum
.
So really, no, there's no way to answer your question similarly.
I recognised this problem and created the package polydata, which you can check out on hackage (needs some clearer documentation I admit).
It does allow you to make functions which accept polymorphic functions that you can apply to different types, like so:
g :: (c (a -> a'), c (b -> b')) => Poly c -> (a, b) -> (a' -> b')
g f (x,y) = (getPoly f x, getPoly f y)
Which is very similar to your example.
c
in the above is a constraint, and looking at the type of g
should help you understand what's happening.
Unfortunately, you can't just pass an ordinary function to g
, you have to pass one wrapped in a Poly
, which is non trivial as you don't get type inference for the Poly
constraint (any ideas on how to make this nicer appreciated).
But if you've just got one or a few functions that need this polymorphic behaviour, I wouldn't bother with Poly
. But for example, you're finding this issue coming up a lot (I found it came up a lot in unit testing, which is what inspired the creation of my package), then you might find polydata useful.
There's also heterolist, which I created as an extension to polydata allows you to create lists of mixed types and say, map over them in a type safe way. You might find that useful.
Upvotes: 1
Reputation: 32319
May as well flesh out my comment in an answer. The usual signature one may be tempted to give is
sum :: Num b => ([a] -> b) -> [a] -> [a] -> b
sum f l1 l2 = f l1 + f l2
The problem here is that the two lists must have the same type, which must be the input type of the function. The solution is to tell GHC that the function actually has the more general type forall a. [a] -> b
, which means that we can pick multiple possibly different a
instantiations and they all produce the same b
.
{-# LANGUAGE RankNTypes #-}
sum' :: Num b => (forall a. [a] -> b) -> [c] -> [d] -> b
sum' f l1 l2 = f l1 + f l2
main = print $ sum' length [1,2] ['a','b']
Upvotes: 5