I have a problem with the following function
sum f l1 l2 = (f l1) + (f l2)
It doesn't work with sum length [1,2] ['a','b']
. When I try this I get
No instance for (Num Char) arising from the literal ‘1’
error so the problem is with types. When I try :t
function, I get sum :: Num a => (t -> a) -> t -> t -> a
. So, if I understand this correctly, I can't just use +
function with both numerical and character values at the same time but I lack the deeper understanding of why exactly is this the case and how to fix it.
I tried a couple of things, like using let
for one of the literals or id
function, but this doesn't seem to work. Any help?
In practice, we can see this by substituting: add 1 2 = (+) 1 2 -- Since add = (+), this can be read literally. = 1 + 2 -- This is how operators work in Haskell. = 3 -- Calculation.
The ++ operator is the list concatenation operator which takes two lists as operands and "combine" them into a single list.
(->) is often called the "function arrow" or "function type constructor", and while it does have some special syntax, there's not that much special about it. It's essentially an infix type operator. Give it two types, and it gives you the type of functions between those types.
in goes along with let to name one or more local expressions in a pure function.
When inferring types from your code, GHC will assume that you intend f
to have a relatively simple type, and intend l1
and l2
to have the same type, so that both are suitable as input to f
.
You apparently want to pass a polymorphic f
, that can work on both [Int]
and [Char]
. Depending how general you want to get, here are some options:
Works on lists, f
must work on any list regardless of element type:
sum0 :: (forall x. [x] -> Int) -> [a] -> [b] -> Int
sum0 f l1 l2 = f l1 + f l2
Works on lists & other Foldable
types (Vector, Set, Matrix), as long as both inputs are the same Foldable
. The first argument can be length
, or something specific to the choice of Foldable
, like Set.size
.
sum1 :: (Num n, Foldable f) => (forall x. f x -> n) -> f a -> f b -> n
sum1 f l1 l2 = f l1 + f l2
Allow l1
and l2
to be different Foldable
types. f
must work for any foldable. length
still qualifies, but Set.size
isn't general enough.
sum2 :: (Num n, Foldable s, Foldable t) => (forall f x. Foldable f => f x -> n) -> s a -> t b -> n
sum2 f l1 l2 = f l1 + f l2
In practice, with a function this small, I think it's easier just to write length l1 + length l2
at each use site than to define a function with any of the complex types above. But it's nice to know we can write these types when we want to.
One approach (somewhat ad hoc) would be to alter the list values:
> sum length (map Right [1,2]) (map Left ['a', 'b'])
4
Instead of arguments of type [Char]
and Num a => [a]
, we have arguments of the common type Num b => [Either b Char]
. This satisfies the inferred constraint (imposed by the fact that f
is applied to both l1
and l2
) that the second and third arguments have the same type.
> :t sum
sum :: Num a => (t -> a) -> t -> t -> a
In fact, because we know that length
doesn't care about the contents of its argument, we could discard any information about what is in the lists and map the same function over both:
> let foo = const () in sum length (map foo [1,2]) (map foo ['a', 'b'])
4
Since const ()
ignores its argument and returns ()
, you simply replace each list with a equally long list of ()
:
> map (const ()) [1,2]
[(),()]
> map (const ()) ['a','b']
[(),()]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With