Last active
August 29, 2015 14:16
-
-
Save cqfd/d9199e489cfce86f6063 to your computer and use it in GitHub Desktop.
Notes for a Hacker School talk on functors, applicatives, etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE DeriveFunctor #-} | |
module Family where | |
import Control.Applicative | |
import Data.Monoid | |
-- Functors | |
-- | |
-- Lift a function (a -> b) to (f a -> f b) | |
-- | |
-- (a -> b) -> ([] a -> [] b) | |
-- (a -> b) -> (Maybe a -> Maybe b) | |
-- (a -> b) -> (Either x a -> Either x b) | |
-- (a -> b) -> (IO a -> IO b) | |
-- (a -> b) -> (Parser a -> Parser b) | |
-- (a -> b) -> (Random a -> Random b) | |
-- | |
-- Weirder... | |
-- (a -> b) -> ((e -> a) -> (e -> b)) | |
-- | |
-- might look more familiar like this... | |
-- (a -> b) -> (e -> a) -> e -> b | |
-- | |
-- instance Functor ((->) e) where | |
-- fmap f a = f . a | |
-- | |
-- Composition! | |
-- IO [a] = IO ([] a) | |
-- | |
-- IO is a functor, and so is []. Is their composition a functor too? | |
-- | |
-- fmap (fmap toUpper) getLine | |
newtype Compose f g a = Compose { getCompose :: (f (g a)) } | |
instance (Functor f, Functor g) => Functor (Compose f g) where | |
fmap f (Compose x) = Compose $ fmap (fmap f) x | |
-- Wait a minute. What isn't a functor? | |
-- | |
-- One nice example: (a -> a) | |
-- | |
-- Given an (a -> a) and an (a -> b), can you get a (b -> b)? Nope! There's | |
-- just no where to put a b input. | |
-- | |
-- Applicatives | |
-- | |
-- Functors let you lift an (a -> b) to an (f a -> f b). | |
-- | |
-- Applicatives extend this idea to arbitrary arities. | |
-- | |
-- a -> f a | |
-- (a -> b) -> (f a -> f b), just like fmap | |
-- (a -> b -> c) -> (f a -> f b -> f c) | |
-- etc. | |
-- | |
-- Recall that with a functor, you can take an f a, tweak it with (a -> b), and | |
-- get an f b. But unfortunately no way to combine multiple functory values, | |
-- at least not with just the abstract functor interface. (Specific examples of | |
-- functors may of course come with means of combination.) | |
-- | |
-- Applicatives let you combine multiple functory values with a pure function. | |
-- | |
-- liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c | |
-- | |
data User = User | |
{ userName :: String | |
, userId :: Int | |
} deriving Show | |
-- randomUser :: Random User | |
-- randomUser = liftA2 User randomString randomInt | |
-- | |
-- userParser :: Parser User | |
-- userParser = liftA2 User parseString parseInt | |
-- | |
-- userFromTheOutsideWorld :: IO User | |
-- userFromTheOutsideWorld = liftA2 User getLine readLine | |
-- | |
-- lotsOfUsers :: [] User | |
-- lotsOfUsers = liftA2 User someNames someNumbers | |
-- | |
-- almostAUser :: (String, Int) -> User | |
-- almostAUser = liftA2 User fst snd | |
-- Composition! | |
-- | |
-- Suppose we have a [IO String] and a [IO Int] | |
-- | |
-- Can we get a [IO User]? | |
-- What about an IO [User]? | |
-- | |
-- What if we have an IO [String] and an IO [Int]? | |
-- | |
-- We've seen how you can squint and think of an IO a as "more or less" an a, | |
-- and how you can squint and think of an [a] as "more or less" an "a" (in a | |
-- "many worlds" kind of way). | |
-- | |
-- So presumably you ought to be able to "squint twice": | |
-- | |
-- ??? :: IO [String] -> IO [Int] -> IO [User] | |
-- | |
instance (Applicative f, Applicative g) => Applicative (Compose f g) where | |
pure = Compose . pure . pure | |
(Compose f) <*> (Compose x) = Compose $ liftA2 (<*>) f x | |
class MultiFunctor f where | |
liftZero :: a -> f a | |
liftTwo :: (a -> b -> c) -> f a -> f b -> f c -- aka liftA2 | |
-- pure = liftZero | |
-- f <*> x = liftTwo ($) f x | |
instance (MultiFunctor f, MultiFunctor g) => MultiFunctor (Compose f g) where | |
liftZero = Compose . liftZero . liftZero | |
liftTwo f (Compose a) (Compose b) = Compose $ liftTwo (liftTwo f) a b | |
-- Interestingly, we only need liftZero and liftTwo to lift all the other | |
-- arities. E.g. here's lifting three things. | |
liftThree :: MultiFunctor f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d | |
liftThree f fa fb fc = liftTwo (\a (b, c) -> f a b c) fa (liftTwo (,) fb fc) | |
-- Wait a minute. When is a functor *not* an applicative too? | |
-- | |
-- One nice example: tagged values (aka tuples) | |
-- | |
-- Think of a value of type (t, a) as an a tagged with a t. | |
-- | |
-- instance Functor ((,) t) where | |
-- fmap f (t, a) = (t, f a) | |
-- | |
-- In order for tagged values to be applicatives, you'd need to be able to | |
-- combine them somehow. If you have a (t, a) and a (t, b) lying around, and a | |
-- function (a -> b -> c), you could use the function to transform the a and | |
-- the b into a c, but what do you do with their tags? And even if you could | |
-- figure out how to combine them, what would liftZero :: a -> (t, a) do? Which | |
-- tag would it pick? | |
-- | |
-- If the tags are monoids though... | |
instance Monoid t => MultiFunctor ((,) t) where | |
liftZero a = (mempty, a) | |
liftTwo f (t, a) (t', b) = (t <> t', f a b) | |
-- instance Monoid t => Applicative ((,) t) where | |
-- pure a = (mempty, a) | |
-- (t, f) <*> (t', x) = (t <> t', f x) | |
-- Here's a related (although admittedly weird-looking) idea about linking | |
-- monoids and applicatives. | |
newtype Constant x a = Constant { getConstant :: x } -- c.f. Const | |
-- A Constant x a is a weird value that doesn't contain anything a-related at | |
-- all. It's the "trivial" functor. | |
instance Functor (Constant x) where | |
fmap f (Constant x) = Constant x | |
-- By itself, Constant x isn't an applicative, because pure would have to pick | |
-- an x. If the x parameter is a monoid though... | |
instance Monoid x => Applicative (Constant x) where | |
pure _ = Constant mempty | |
(Constant x) <*> (Constant x') = Constant (x <> x') | |
-- Attach pure values to a functor. | |
data Lift f a = Pure a | Other (f a) | |
instance Functor f => Functor (Lift f) where | |
fmap f (Pure a) = Pure (f a) | |
fmap f (Other a) = Other (fmap f a) | |
instance Applicative f => Applicative (Lift f) where | |
pure = Pure | |
(Pure f) <*> (Pure x) = Pure (f x) | |
(Pure f) <*> (Other x) = Other (fmap f x) | |
(Other f) <*> (Pure x) = Other (fmap ($ x) f) | |
(Other f) <*> (Other x) = Other (f <*> x) | |
-- We can use the Constant applicative to accumulate a monoid representing | |
-- errors (say, [String]). We basically add pure (aka successful) values to an | |
-- applicative that accumulates the errors. | |
type Errors e a = Lift (Constant e) a | |
getErrors :: Errors e a -> Either e a | |
getErrors (Pure a) = Right a | |
getErrors (Other (Constant e)) = Left e | |
-- We've just shown that every monoid gives us a trivial applicative. The | |
-- reverse works too! You just lift the monoid stuff into the applicative. | |
-- instance (Applicative f, Monoid m) => Monoid (f m) where | |
-- mempty = pure mempty | |
-- mappend = liftA2 mappend | |
-- In addition to lifting monoids, applicatives also lift numbers! | |
-- | |
-- {-# FlexibleInstances #-} | |
-- instance (Applicative f, Num a) => Num (f a) where | |
-- (+) = liftA2 (+) | |
-- (*) = liftA2 (*) | |
-- (-) = liftA2 (-) | |
-- abs = fmap abs | |
-- fromInteger i = pure (fromInteger i) | |
-- Not sure if this is a good idea or not, but it works :) | |
instance Num a => Num (IO a) where | |
(+) = liftA2 (+) | |
(*) = liftA2 (*) | |
(-) = liftA2 (-) | |
abs = fmap abs | |
signum = fmap signum | |
fromInteger = pure . fromInteger | |
intFromTheOutsideWorld :: IO Int | |
intFromTheOutsideWorld = 1 + readLn |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment