Question

In my code I need to sort a list of (Int, Int)'s by the value of the second element.

It's easy enough to do sorted = sortBy (\(_,a) (_,b) -> compare a b) listOfPairs, but I hate writing lambda's like this and I need to use this same lambda in a few places in my code.

So I tried to create two helpful functions that I will end up using in many places as follows (and I couldn't find them in the prelude):

-- apply a function of one argument to two different arguments and return a pair
fBoth :: (a -> b) -> a -> a -> (b,b)
fBoth f a b = (f a, f b)

-- pass elements of a pair to a function of two arguments
expandPair :: (a -> b -> c) -> (a,b) -> c
expandPair f (a,b) = f a b

Now I thought I would be able to compose something together to pass to sortBy instead of an ugly "extractor" lambda. But I cant quite get the types to line up.

I feel like I want to compose (expandPair compare) with (fBoth snd), because :t expandPair compare = (a,a) -> Ordering and :t fBoth snd = (a,b) -> (a,b) -> (b,b). I would think that the resulting type of this composition would be (a,b) -> (a,b) -> Ordering which I can pass to sortBy, but it doesn't quite work.

If I try to do sortBy ((expandPair compare) . (fBoth snd)) [(1,2),(3,4)] it gives me type errors. But what confuses me is that if I do expandPair compare $ fBoth snd (1,2) (3,4) it actually works and yields LT as expected.

So clearly I'm not understanding something about function composition here... I get the whole "g composed with f = g(f(x))" but I'm having trouble making it work for me here.

Alternatively, if there's an even simpler way to accomplish this particular task of sorting a list of pairs by the second element, I'd be interested in hearing that as well.

Était-ce utile?

La solution

First of all, your expandPair function is literally the same definition as uncurry, which is in Prelude.

The problem here is that fboth snd takes 2 arguments, so the normal composition operator is not quite suited for this task. However, you can make a rather strange looking operator that lets you "compose" a function that takes two arguments with a function that takes 1 argument:

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.).(.)

The easy mnemonic for remembering this is that the function on the side with one dot takes 1 argument, of the same type as the output of the function that takes two arguments, which is on the side with two dots. You can then write this as

sortBy (uncurry compare .: fboth snd) [(1, 2), (3, 4)]

Now, I will say that it's best not to really think about how the .: operator works, just look at its type rather than its definition. It does come in handy fairly often, I use it pretty regularly and I certainly didn't come up with this operator.


You can also implement it as

> :m +Data.Function
> sortBy (compare `on` snd) ...
-- sortBy (on compare snd) ...

But as @mhwombat has pointed out, Data.Ord has an alias for on compare called comparing:

> :m +Data.Ord
> sortBy (comparing snd) ...

which is my preference.


Something that might make this operator a little more clear:

> :t (id .: (,))
a -> b -> (a, b)
> (id .: (,)) 1 2
(1, 2)
> (fst .: (,)) 1 2
1
> (snd .: (,)) 1 2
2
> (negate .: (+)) 1 2
-3

Autres conseils

In the particular case of compare what your're looking an pplication of th on function from Data.Function:

>>> :t (`on` snd)
(`on` snd) :: (b -> b -> c) -> (a, b) -> (a, b) -> c

>>> :t (compare `on` snd)
(compare `on` snd) :: Ord b => (a, b) -> (a, b) -> Ordering
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top