9

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?

3
  • 2
    No that is correct, since length :: [Int] -> Int is not the same function as length :: [Char] -> Int. Here you thus force f to to have a specific type, but that is not applicable to both inputs l1 and l2. Commented Nov 26, 2018 at 19:12
  • Oh, so I misunderstood it after all. Thank you. How can I fix it? I tried using let for one of the inputs so that they don't have the same type when using length function but that doesn't work. Commented Nov 26, 2018 at 19:25
  • Tuples handle multiple types. Lists are typed. Two types don't mix. sequence [length.fst,length.snd] ([1,2], ['a','b']) Commented Nov 26, 2018 at 22:54

2 Answers 2

10

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Which language extension do you use for this to work out..?
@Redu Should be RankNTypes, I'm pretty sure.
RankNTypes is what I used. Thank you both for catching that missing piece of my answer.
3

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']
[(),()]

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.