9

I want to do something fairly simple; I am using the operator (++) with Data.Map insertWith, and it works fine, but I want to eliminate duplicates in the value created, so want to compose it with nub.

I tried (nub (++)), (nub $ (++)), (nub . (++)), all to no avail, in that the type of (++) does not match the expected type of nub ( [a] ).

I could of course define an auxiliary function or a lambda, but I think that probably there is a composition which would be clearer.

Hints please!

guthrie
  • 4,529
  • 4
  • 26
  • 31

5 Answers5

11

You can write this as

((nub .) .) (++)

Example:

Prelude Data.List> ((nub .) .) (++) [1,2,3] [3,4,5]
[1,2,3,4,5]

In general, you have

(f . ) g x = f (g x) 
((f . ) . ) g x y = f (g x y) 
(((f . ) . ) . ) g x y z = f (g x y z) 
((((f . ) . ) . ) . ) g x y z v = f (g x y z v)
...

Here's the derivation of this identity for ((nub .) .):

(f . g) x = f (g x)

(nub .) :: Eq a1 => (a -> [a1]) -> a -> [a1] 
(nub .) = \g x -> (nub (g x))

((nub .) .) :: Eq a2 => (a -> a1 -> [a2]) -> a -> a1 -> [a2]
((nub .) .) = ((\g x -> (nub (g x))) .) = (\g' x' -> (\g x -> (nub (g x))) (g' x'))
            = \g' x' x -> (nub ((g' x') x))

There is a nice article about this (and related) idioms, but it's in Russian :-(

Mikhail Glushenkov
  • 14,928
  • 3
  • 52
  • 65
  • Very nice, Thanks - for the answer and the derivation. (Your last line of the derivation looks like it may be in Russian!?) – guthrie Jul 06 '11 at 20:37
6

What you want seems to be a composition of binary and unary functions, like this:

compose :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
compose unary binary a b = unary (binary a b)

And you ask for a point-free version (without mentioning of a and b variables). Let's try and eliminate them one by one. We'll start with b, using the fact that f (g x) = f . g:

compose unary binary a = unary . binary a

a is next. Let's desugar the expression first:

compose unary binary a = ((.) unary) (binary a)

And apply the same composition rule again:

compose unary binary = ((.) unary) . binary

This can be further written as:

compose unary = (.) ((.) unary)

Or even as

compose = (.) . (.)

Here, each (.) 'strips' an argument off the binary function and you need two of them because the function is binary. This idiom is very useful when generalised for any functor: fmap . fmap (note that fmap is equivalent to . when function is seen as a functor). This allows you to 'strip' any functor off, for example you can write:

incrementResultsOfEveryFunctionInTwoDimentionalList :: [[String -> Integer]] -> [[String -> Integer]]
incrementResultsOfEveryFunctionInTwoDimentionalList = fmap . fmap . fmap $ (+1)

So, your result becomes:

(fmap . fmap) nub (++)

Edit:

I think I have found the answer my brain was trying to reproduce: Haskell function composition operator of type (c→d) → (a→b→c) → (a→b→d)

Community
  • 1
  • 1
Rotsor
  • 13,655
  • 6
  • 43
  • 57
  • Thanks, I like your derivation & logic. I now remember that I had figured all this out before for another application, and forgotten it. I think it may be convenient to think of each application of "." as an argument insertion point. – guthrie Jul 08 '11 at 11:51
3

This problem is solved in a particularly simple and beautiful way by semantic editor combinators. Confer:

Your final composition would look like:

(result.result) nub (++)
Community
  • 1
  • 1
luqui
  • 59,485
  • 12
  • 145
  • 204
1

You can use the somewhat funny-looking (.).(.) combinator:

Prelude> :set -XNoMonomorphismRestriction
Prelude> :m + Data.List
Prelude Data.List> let f = ((.).(.)) nub (++)
Prelude Data.List> :t f
f :: Eq a => [a] -> [a] -> [a]
Prelude Data.List> f "ab" "ac"
"abc"

It's probably gonna be more readable to just use a lambda or an auxilliary function in a where-clause, though.

hammar
  • 138,522
  • 17
  • 304
  • 385
1

I don't think the composition operator you want exists as a single function in any standard library. The shortest way to write it is probably ((.).(.)). Using the Functor definition for ((->) t), you can also write it as fmap . fmap or, if you prefer fmap fmap fmap.

All of the above are pretty cryptic, but the idiom is common enough that many people will recognize what you're doing.

By the way, you may want to avoid calling functions of two arguments "dyadic" in Haskell, because if you extend that terminology to functions of one argument you're going to really confuse people.

See also this question for some related discussion.

You can also find lots of combinators with very intuitive names in this library.

Community
  • 1
  • 1
C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 1
    @luqui: Sadly the other common term for 2-ary functions is "binary" which is itself overloaded jargon, but more likely to be clear from context. Using "monadic" instead of "unary" is just begging for mass confusion, despair, civil unrest, and general inconvenience. – C. A. McCann Jul 06 '11 at 17:35
  • @all - thanks for the clarifications (?!) - I'll try to avoid talking about arguments at all, since they seem .. argumentative. :-) – guthrie Jul 06 '11 at 20:34
  • 2
    @guthrie: On the other hand, avoiding arguments entirely is kind of [pointless](http://www.haskell.org/haskellwiki/Pointfree). Ha, ha ha. Yeah, my pun license is gonna get revoked at this rate. – C. A. McCann Jul 06 '11 at 20:39