'Applicative for a user defined type
I'm trying to write Applicative for this type
data Choice a = ColumnA a | ColumnB a
I wrote a Functor instance:
instance Functor Choice where
fmap f (ColumnA a ) = (ColumnA (f a) )
fmap f (ColumnB a ) = (ColumnB (f a) )
Now I want to write Applicative where ColumnB is considered "a correct value" and ColumnA is considered to be some kind of an error.
I tried
instance Applicative Choice where
pure = ColumnB
ColumnB f <*> r = fmap f r
ColumnA f <*> _ = ColumnA f --- this does not work
How can I make it work ?
Solution 1:[1]
Let's rename your data constructors to express your intent properly, as
data Choice a = Bad a | Good a
Your Functor instance keeps the taint on the values,
instance Functor Choice where
fmap f (Bad x) = Bad (f x)
fmap f (Good x) = Good (f x)
so let's just do the same for the Applicative, without being skimpy with our clauses:
instance Applicative Choice where
pure x = Good x -- fmap f == (pure f <*>) is the Law
Good f <*> Good x = Good (f x)
Good f <*> Bad x = Bad (f x)
Bad f <*> Good x = Bad (f x)
Bad f <*> Bad x = Bad (f x)
As was pointed in the comments, this interprets Choice a as isomorphic to Writer All a, meaning, Choice a values are really just like (Bool, a) with (False, x) corresponding to Bad x and (True, x) corresponding to Good x. Naturally we only consider values to be Good if everything in their provenance was Good as well.
Solution 2:[2]
If ColumnA is considered some kind of error, you can not let it wrap an a value. Indeed. The idea of (<*>) is that it takes a Choice (x -> y) and Choice x, and returns a Choice y. But if you have a ColumnA that wraps a function of type x -> y, and you have at the right hand a Choice x, then it thus should return a Choice y, not a Choice x.
What you could do is define a type with two type parameters, for example:
data Choice a b = ColumnA a | ColumnB b
then you only perform a mapping over the ColumnB b data constructor:
instance Functor (Choice a) where
fmap _ (ColumnA e) = ColumnA e
fmap f (ColumnB x) = ColumnB (f x)
and then we can define an Applicative instance as:
instance Applicative (Choice a) where
pure = ColumnB
ColumnB f <*> ColumnB x = ColumnB (f x)
ColumnA e <*> _ = ColumnA e
_ <*> ColumnA e = ColumnA e
Such instance for a Functor and Applicative however already exist: this is how it is defined on the Either data type.
Solution 3:[3]
I made a package for deriving Applicative for sum types:idiomatic.
Choice can be biased towards the left or the right, if it is biased towards the left then ChoiceA is the pure constructor and combining A an B defects to ChoiceB:
{-# Language DerivingVia #-}
{-# Language DerivingStrategies #-}
{-# Language DeriveGeneric #-}
{-# Language DataKinds #-}
import Generic.Applicative
import GHC.Generics
data Choice a = ColumnA a | ColumnB a
deriving
stock (Show, Generic1)
-- pure :: a -> Choice a
-- pure = ColumnA
--
-- liftA2 :: (a -> b -> c) -> (Choice a -> Choice b -> Choice c)
-- liftA2 (·) (ColumnA a) (ColumnA a') = ColumnA (a · a')
-- liftA2 (·) (ColumnA a) (ColumnB b) = ColumnB (a · b)
-- liftA2 (·) (ColumnB b) (ColumnA a) = ColumnB (b · a)
-- liftA2 (·) (ColumnB b) (ColumnB b') = ColumnB (b · b')
deriving (Functor, Applicative)
via Idiomatically Choice '[LeftBias Id]
and right-bias means ChoiceB is the pure constructor and combining A an B defects to ChoiceA:
-- pure :: a -> Choice a
-- pure = ColumnB
--
-- liftA2 :: (a -> b -> c) -> (Choice a -> Choice b -> Choice c)
-- liftA2 (·) (ColumnA a) (ColumnA a') = ColumnA (a · a')
-- liftA2 (·) (ColumnA a) (ColumnB b) = ColumnA (a · b)
-- liftA2 (·) (ColumnB b) (ColumnA a) = ColumnA (b · a)
-- liftA2 (·) (ColumnB b) (ColumnB b') = ColumnB (b · b')
deriving (Functor, Applicative)
via Idiomatically Choice '[RightBias Id]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Will Ness |
| Solution 2 | luqui |
| Solution 3 | Iceland_jack |
