Skip to content

Instantly share code, notes, and snippets.

@afmika
Last active April 22, 2024 13:41
Show Gist options
  • Save afmika/55f243f8b663a3df77c780cb093f853e to your computer and use it in GitHub Desktop.
Save afmika/55f243f8b663a3df77c780cb093f853e to your computer and use it in GitHub Desktop.
Proving that Option is a Functor, a Monad, an Applicative and more!
import Control.Applicative (Alternative)
import Control.Monad
import GHC.Base (Alternative (empty, (<|>)))
-- Proving that Option is a Functor, a Monad, an Applicative and more!
data Option a = Some a | None
deriving (Show, Eq)
-- 1. it is a functor! (surprise)
instance Functor Option where
fmap f (Some a) = Some (f a)
fmap f None = None
-- 2. we can also prove that it is Applicative! (requires 1.)
-- This allows chaining, and provide a cleaner way to apply a function to a chain via fmap (<$>)
-- (+) <$> Some 1 <*> Some 2 = Some (1 + 2) = Some 3
-- (+) <$> None <*> Some 1 => None
-- (,) <$> Some 1 <*> Some 2 => Some (1, 2)
instance Applicative Option where
pure = Some -- identity but spicy
Some a <*> Some b = Some (a b)
None <*> _ = None
_ <*> None = None
-- 3. Also a semigroup! A trivial operator that is associative
-- We define the generalized form here, the type constraint `Semigroup a` is to ensure that <> also is defined
-- for the wrapped types (we don't really have to define each instance case, we can if needed but..)
-- eg. Some [1, 2] <> Some [3] = Some [1, 2, 3] since [a] is a semigroup whose <> is defined as concat
instance (Semigroup a) => Semigroup (Option a) where
Some a <> Some b = Some (a <> b)
None <> _ = None
_ <> None = None
-- 4. cherry on the top, Monoid requires Semigroup definition
-- dumbed down := Semigroup (binary operator) + Monoid (id element) -> A Group!
-- Some [1, 2] <> mempty = Some [1, 2]
-- we basically defined a no-op (<>) :: Option a -> Option a
instance (Monoid a) => Monoid (Option a) where
mempty = Some mempty -- mempty of the wrapped a (which is also a monoid)
-- 5. Option is a Monad, as it encodes failure, enabling it to operate the monad way
-- makes sense
instance Monad Option where
(>>=) None f = None
(>>=) (Some a) f = f a
-- 6. additional interfaces for a monad
-- Recover error when the pattern on the lhs of <- does not match
instance MonadFail Option where
fail _ = None
-- eg. Some 123 <|> None = None <|> Some 123 = Some 123
instance Alternative Option where
empty = None -- identity of <|>
None <|> b = b
a <|> _ = a
-- this one requires Alternative, literally the same but
-- with the constraint that the enderlying type must be a monad
-- The default definition of `mplus` is <|>
instance MonadPlus Option where
mzero = None
None `mplus` b = b
a `mplus` _ = a
-- 7. How about enabling sort for all things that can be ordered?
-- sort <$> Some [4,3,-1,5] => Some [-1,3,4,5]
instance (Ord a) => Ord (Option a) where
compare (Some a) (Some b) = compare a b
compare None None = EQ
compare None (Some _) = LT
compare (Some _) None = GT
-- 8. and so on..
exampleSemigroupApplicativeFunctorMonad :: Option String
-- output => Some "prefonetwo"
exampleSemigroupApplicativeFunctorMonad =
let a = Some "pref" <> Some "one" -- Semigroup binop which is concat for String => Some "prefone"
b = fail "bad" <|> mzero <|> pure "two" -- Alternative binop => Some "two"
in (++) <$> a <*> b -- Functor and Applicative
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment