> Let's say that I have a function x that returns a Foo, a function y that takes a Foo and returns a Bar, and a function z that takes a Bar and returns a Baz. This is perfect: we just call z(y(x)).
update: I created an interactive tutorial[0]
Alright, here is my code (I can verify it is runnable/working on ghc 7.8):
data Foo = Foo deriving Show
data Bar = Bar deriving Show
data Baz = Baz deriving Show
x :: Foo
x = Foo
y :: Foo -> Bar
y _ = Bar
z :: Bar -> Baz
z _ = Baz
> Then we discover a corner case, or the requirements change, and now there's a situation where x can't return a valid Foo. In Haskell, then, we have x return a Maybe Foo, and y take a Maybe Foo.
I'm only going to make one change to x:
- x :: Foo
- x = Foo
+ x :: Maybe Foo
+ x = Just Foo
You don't have to change caller y to take a maybe Foo for one, you can leave it as is and use:
λ> fmap y x
Let me remind you of the type of fmap:
fmap :: Functor f => (a -> b) -> f a -> f b
specialized to the case of Maybe's functor instance and our types:
fmap :: (Foo -> Bar) -> Maybe Foo -> Maybe Bar
> Then y either handles the Nothing case using a pattern, or else returns a Maybe Bar.
In this case you can see by the definition of fmap a Maybe Bar is returned.
> If y returns a Maybe Bar, then z handles the Nothing, or else returns a Maybe Baz.
Just as we don't have to change our definition of z.
Our final composed function is just:
z . y <$> x
So the final code looks like this:
import Control.Applicative (<$>) -- must import explicitly before GHC 7.10
data Foo = Foo deriving Show
data Bar = Bar deriving Show
data Baz = Baz deriving Show
x :: Maybe Foo
x = Just Foo
y :: Foo -> Bar
y _ = Bar
z :: Bar -> Baz
z _ = Baz
main = print (z . y <$> x)
It sounds like you previously thought all the functions had to be updated and it had to look something like this:
data Foo = Foo deriving Show
data Bar = Bar deriving Show
data Baz = Baz deriving Show
x :: Maybe Foo
x = Just Foo
y :: Maybe Foo -> Maybe Bar
y _ = Just Bar
z :: Maybe Bar -> Maybe Baz
z _ = Just Baz
main = print (z . y $ x)
Does this make the composabiltity clear? Is it clear how fmap (aka <$>) eliminated the problem having to update all of those functions whereas in a less composable language it would be necessary?
If you are interested in more information about functor, applicative, and monad check out Adits awesome "Functors, applicatives, and monads in pictures" tutorial[1]. The pictures/diagrams in it helped me really make sense of applicatives, functors, and monads.
0: https://www.fpcomplete.com/user/codygman/real-world-fmap-exa...
1: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_...