Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Haskell addition function with both Num and Char values

Tags:

haskell

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?

like image 890
SuperM4n Avatar asked Nov 26 '18 19:11

SuperM4n


People also ask

How do you do an addition in Haskell?

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.

What does ++ do Haskell?

The ++ operator is the list concatenation operator which takes two lists as operands and "combine" them into a single list.

What does -> mean in Haskell?

(->) 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.

What does in do in Haskell?

in goes along with let to name one or more local expressions in a pure function.


2 Answers

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.

like image 88
bergey Avatar answered Oct 10 '22 17:10

bergey


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']
[(),()]
like image 32
chepner Avatar answered Oct 10 '22 17:10

chepner