1

Would someone help explain/breakdown the following code:

λ> fmap fmap (,) <*> Just . reverse $ "stackoverflow"
Just ("stackoverflow","wolfrevokcats")

It would be great if someone could walk me through their process of understanding the mechanics of this kind of code.

matt
  • 1,817
  • 14
  • 35

1 Answers1

10

Let's try to decode this pointless style obfuscation:

fmap fmap (,) <*> Just . reverse $ "stackoverflow"

First step: how this is parsed? Well, $ has low precedence, so it's

(fmap fmap (,) <*> Just . reverse) $ "stackoverflow"

Also, I guess that the infix <*> has a lower precedence than . (one could check using the documentation or :i in GHCi). So, we get

((fmap fmap (,)) <*> (Just . reverse)) $ "stackoverflow"

Let's beta-reduce the $

((fmap fmap (,)) <*> (Just . reverse)) "stackoverflow"

So, the result of <*> is a function. This means that we are working in the (->) a applicative. There, (<*>) = \x y z -> x z (y z) AKA the S combinator from lambda calculus. We can beta-reduce that:

(\z -> (fmap fmap (,)) z ((Just . reverse) z)) "stackoverflow"

More betas, removing parentheses when we can:

fmap fmap (,) "stackoverflow" ((Just . reverse) "stackoverflow")

More simplification:

fmap fmap (,) "stackoverflow" (Just (reverse "stackoverflow"))
fmap fmap (,) "stackoverflow" (Just "wolfrevokcats")

Now, the fmap fmap (,) part. The (,) argument is a function, so this means that the first fmap is working in the (->) a functor. There, fmap = (.). So, it's just obfuscation for

(.) fmap (,) "stackoverflow" (Just "wolfrevokcats")
(fmap . (,)) "stackoverflow" (Just "wolfrevokcats")
(fmap ((,) "stackoverflow")) (Just "wolfrevokcats")
fmap ((,) "stackoverflow") (Just "wolfrevokcats")

Now, the second argument of fmap is of type Maybe String, so the fmap is working in the Maybe functor. There, fmap f (Just x) = Just (f x). We get

Just (((,) "stackoverflow") "wolfrevokcats")
Just ((,) "stackoverflow" "wolfrevokcats")
Just ("stackoverflow", "wolfrevokcats")

Conclusion: pointless code is often pointless. To decode this snippet we had to run the type inferer in our head, recall three specific instances (even if they are pretty standard), and cope with precedence. No one should ever use this style except for deliberate obfuscation. (Surely not as a hiring puzzle!)

chi
  • 111,837
  • 3
  • 133
  • 218
  • Thank you, that is an extremely helpful answer. How would you write the function then... something like `liftA2 (<$>) (,) (Just . reverse)` ? – matt Jul 04 '17 at 23:14
  • 3
    @matthias `(\xs -> Just (xs, reverse xs))` would probably be ideal for readability. – 4castle Jul 04 '17 at 23:45
  • 3
    @matthias As a thumb rule, when pointfree code is longer than the pointful version, it should not be used. 4castle's version is perfectly clear, and even shorter. If I really wanted a pointfree one, I'd use `Just . (id &&& reverse)`. – chi Jul 05 '17 at 08:02