Still not a hundred percent shure how to make instances of the more complex types. Have this:
data CouldBe a = Is a | Lost deriving (Show, Ord)
Made an instance of Functor
, using Maybe
as an example:
instance Functor CouldBe where
fmap f (Is x) = Is (f x)
fmap f Lost = Lost
For doing something like this:
tupleCouldBe :: CouldBe a -> CouldBe b -> CouldBe (a,b)
tupleCouldBe x y = (,) <$> x <*> y
CouldBe
needs to be an instance of Applicative
, but how would you go about that? Sure I can look it up and copy it, but I want to learn the process behind it and finally end up with the instance
declaration of CouldBe.
You just write it out, following the types:
instance Applicative CouldBe where
{-
Minimal complete definition:
pure, ((<*>) | liftA2)
pure :: a -> f a
pure :: a -> CouldBe a
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 :: (a -> b -> c) -> CouldBe a -> CouldBe b -> CouldBe c
-}
pure a = fa
where
fa = ....
liftA2 abc fa fb = fc
where
fc = ....
According to
data CouldBe a = Is a | Lost
our toolset is
Is :: a -> CouldBe a
Lost :: CouldBe a
but we can also use pattern matching, e.g.
couldBe is lost (Is a) = is a
couldBe is lost (Lost) = lost
couldBe :: ? -> ? -> CouldBe a -> b
couldBe :: ? -> b -> CouldBe a -> b
couldBe :: (a -> b) -> b -> CouldBe a -> b
So,
-- pure :: a -> f a
pure :: a -> CouldBe a
matches up with
Is :: a -> CouldBe a
so we define
pure a = Is a
Then, for liftA2
, we follow the data cases:
-- liftA2 :: (a -> b -> c) -> f a -> f b -> f c
-- liftA2 :: (a -> b -> c) -> CouldBe a -> CouldBe b -> CouldBe c
liftA2 abc Lost _ = ...
liftA2 abc _ Lost = ...
liftA2 abc (Is a) (Is b) = fc
where
c = abc a b
fc = .... -- create an `f c` from `c`:
-- do we have a `c -> CouldBe c` ?
-- do we have an `a -> CouldBe a` ? (it's the same type)
But in the first two cases we don't have an a
or a b
; so we have to come up with a CouldBe c
out of nothing. We do have this tool in our toolset as well.
Having completed all the missing pieces, we can substitute the expressions directly into the definitions, eliminating all the unneeded interim values / variables.
With my new idiomatic package you can derive Applicative
for sums.
{-# Language DataKinds #-}
{-# Language DeriveGeneric #-}
{-# Language DerivingVia #-}
{-# Language StandaloneKindSignatures #-}
import Data.Kind
import GHC.Generics
import Generic.Applicative
type CouldBe :: Type -> Type
data CouldBe a = Is a | Lost
deriving
stock (Eq, Ord, Show, Generic1)
-- > pure @CouldBe 10
-- Is 10
-- > liftA2 (+) (Is 1) Lost
-- Lost
-- > liftA2 (+) Lost (Is 10)
-- Lost
deriving (Functor, Applicative)
via Idiomatically CouldBe '[LeftBias Terminal]
-- > tupleCouldBe Lost Lost
-- Lost
-- > tupleCouldBe (Is 1) Lost
-- Lost
-- > tupleCouldBe Lost (Is 20)
-- Lost
-- > tupleCouldBe (Is 1) (Is 20)
-- Is (1,20)
tupleCouldBe :: CouldBe a -> CouldBe b -> CouldBe (a, b)
tupleCouldBe = liftA2 (,)
Why does this work? Being left-biased means we choose the Is
constructor to be the pure
constructor.
This means we "defect" from that constructor, when lifting over different constructors.
Terminal
describes how we transform any Applicative
into Const mempty
data Terminal
instance (Applicative f, Monoid m) => Idiom Terminal f (Const m) where
idiom :: f ~> Const m
idiom = mempty
which in this case discards the argument of Is
mapping to Lost
.
Note there is no way to define a right-biased definition of CouldBe
, since it would require an applicative morphism that produces an a
out of nothing
via Idiomatically CouldBe '[RightBias ..]
idiom :: Const () ~> Identity
idiom (Const ()) = Identity ??
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With