Last active
April 22, 2024 13:41
-
-
Save afmika/55f243f8b663a3df77c780cb093f853e to your computer and use it in GitHub Desktop.
Proving that Option is a Functor, a Monad, an Applicative and more!
This file contains 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
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