15

I was playing around with a simple function for someone else's Stack Overflow question, and wrote the expression:

f a x ++ f a y

Obviously this is the best way to write that expression in real life, given I have all those variables in scope anyway, but I saw the duplication of f a, and thought "Hey, maybe you can remove that with the Applicative instance for functions". I wound up with:

liftA2 (++) (flip f x) (flip f y) a

which is just awful. Is there some nicer way to remove this duplication?Obviously I could also remove the duplication by binding f a to something in a where clause, but this was intended as an exercise in using built-in functions.

amalloy
  • 89,153
  • 8
  • 140
  • 205

4 Answers4

22

You could do

((++) `on` f a) x y

That doesn't use Applicative, though (sorry).

melpomene
  • 84,125
  • 8
  • 85
  • 148
  • I like this answer, though! I guess I wasn't really looking for Applicative answers specifically, so much as ways to use the standard library to remove the duplication. – amalloy Sep 30 '15 at 20:31
21

[...] maybe you can remove that with the Applicative instance for functions.

Do you have to use the Applicative instance of ((->) t)? If you just want to get rid of the duplicated f a, why not use the list monad, instead?

[x, y] >>= f a

or, equivalently,

f a =<< [x, y]

Example:

λ> let f :: Int -> Int -> [Int]; f a x = [a .. x]

λ> f 1 2 ++ f 1 3
[1,2,1,2,3]

λ> [2, 3] >>= f 1
[1,2,1,2,3]

λ> f 1 =<< [2, 3]
[1,2,1,2,3]
jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • It's worth noting that `=<<` is the same as `concatMap` in this situation, so if you want to think about this in terms of list manipulations instead of monads, that's the function to use. – Stone Mason Sep 30 '15 at 22:22
  • @cool_me5000 Sure, but I wanted to emphasize the monadic aspect of the operation. – jub0bs Oct 01 '15 at 06:02
15

Bikeshedding is fun! Another option would be to use the Monoid instance for functions:

(($x) <> ($y)) (f a)
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • 1
    I don't understand this one. Isn't `mappend` composition, for functions? And what happened to the `++`? – amalloy Oct 01 '15 at 01:00
  • @amalloy For functions, `mappend` is pointwise appending: `mappend f g x = mappend (f x) (g x)`. The `(++)` disappears because it is the `mappend` that happens at each point. – Daniel Wagner Oct 01 '15 at 01:52
3

Since the question hinted at a solution using Applicative (although other answers are more elegant)...

((++) <$> ($ x) <*> ($ y)) (f a)
frasertweedale
  • 5,424
  • 3
  • 26
  • 38
  • If you want to get rid of some parentheses, you can write: `(++) <$> ($ x) <*> ($ y) $ f a`. – jub0bs Oct 01 '15 at 14:46
  • 1
    @Jubobs I'm not sure if he's right or not, but I've decided to road-test Gabriel Gonzalez's advice on making Haskell code more readable to non-Haskellers: http://www.haskellforall.com/2015/09/how-to-make-your-haskell-code-more.html. Avoiding `($)` is his top recommendation. – frasertweedale Oct 02 '15 at 07:42
  • Thanks for the link `:)` – jub0bs Oct 02 '15 at 08:48
  • @frasertweedale I personally find the version with `$` much more readable, parenthesis are annoying as you have to match them up with each other, and so as you get more and more of them it becomes progressively more of a pain in the ass to decipher the order of operations. Whereas `$` doesn't need to be matched with anything. It really doesn't take long to figure out how `$` works and I consider that to be a perfectly acceptable small barrier to reading Haskell code. – semicolon Sep 28 '16 at 20:00