Reusing code is only an advantage if you can reuse code to do what you really want.
GHCi> putStrLn =<< getLine Sulfur Sulfur GHCi>
Here (=<<) selects the String result created in the IO context using getLine and passes it to putStrLn , which then prints the specified result.
GHCi> :t getLine getLine :: IO String GHCi> :t putStrLn putStrLn :: String -> IO () GHCi> :t putStrLn =<< getLine putStrLn =<< getLine :: IO ()
Now, in type fmap / (<$>) ...
GHCi> :t (<$>) (<$>) :: Functor f => (a -> b) -> fa -> fb
... it is entirely possible that b will be IO () , and therefore nothing prevents us from using putStrLn with it. But...
GHCi> putStrLn <$> getLine Sulfur GHCi>
... nothing will be printed.
GHCi> :t putStrLn <$> getLine putStrLn <$> getLine :: IO (IO ())
Performing an IO (IO ()) action will not perform an internal IO () action. To do this, we need additional Monad power by replacing (<$>) with (=<<) or, which is the same, using join by the value of IO (IO ()) :
GHCi> :t join join :: Monad m => m (ma) -> ma GHCi> join (putStrLn <$> getLine) Sulfur Sulfur GHCi>
Like me, itβs also difficult for me to understand the background of your question. You seem to expect one of Functor , Applicative and Monad be better than the rest. This is not relevant. We can do more with Applicative than with Functor , and even more with Monad . If you need extra power, use the appropriate class. If not, using a less powerful class will result in simpler, more understandable code and a wider range of available instances.