I plan to showcase an example of the power of typeclasses in a small intro I'll give to Haskell tomorrow.
I thought I'd double check on what I was going to present because I'm not 100% confident.
So, let's say I have two functions:
firstSort :: Int -> Int -> Ordering
firstSort a b = compare (even a) (even b)
secondSort :: Int -> Int -> Ordering
secondSort = compare
Now, I can combine those two sorts thanks to their Monoid instances:
sort :: Int -> Int -> Ordering
sort = firstSort <> secondSort
Now what I'd like to confirm is the process used during the mappend
of the two sorts.
From what I understand, it works this way:
first the instance:
instance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g x
is used. This is done by instanciating the a
of this instance with (Int -> Int)
. Also, the declaration of mappend
got me puzzled for some time since it takes 3 arguments instead of 2 but then I remembered that (a -> b) -> (a -> b) -> (a -> b)
is actually the same as (a -> b) -> (a -> b) -> a -> b
.
Thanks to this instance we obtain that firstSort <> secondSort
is:
\a b -> (firstSort a b) <> (secondSort a b)
then the instance:
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
is used with the specified mappend
on the results of firstSort
and secondSort
So, to sum it up, it would match the Monoid b => Monoid (a -> b)
instance with b = Ordering
and a = (Int -> Int)
and afterwards the Monoid Ordering
instance.
Is that correct?
Yes. You've concluded correctly.
Since Int -> Int -> Ordering
is curried, it can be seen as a function of one argument, which returns another function. I.e.:
Int -> (Int -> Ordering)
Hence the compiler resolves the Monoid
instances in three steps:
Value Type | Monoid Instance
--------------------------|---------------------------
Int -> (Int -> Ordering) | instance Monoid b => Monoid (a -> b)
Int -> Ordering | instance Monoid b => Monoid (a -> b)
Ordering | instance Monoid Ordering
Let's see. I don't think the a
that you want is (Int -> Int)
because function types associate to the right when there are no parantheses. In your case, the types are
firstSort :: Int -> (Int -> Bool)
secondSort :: Int -> (Int -> Bool)
So, something interesting is going on here. When you concatenate the two functions, it's actually doing it twice! First, the a
is just the first Int
and b
is the Int -> Bool
, so we have to use the Monoid structure of Int -> Bool
. But to do that we have to do the exact same thing again, so the a
is Int
and the b
is Bool
.
firstSort <> secondSort = \a -> (firstSort a) <> (secondSort a)
= \a -> (\b -> (firstSort a b) <> (secondSort a b))
But the end result is still the same as the one you derived:
= \a b -> (firstSort a b) <> (secondSort 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