As I am the one who showed you these operators, I'll give a brief explanation as to why I used them.
To review, a functor is a type constructor that lets you use the fmap
function to apply a function to a "wrapped" value. In the specific case of the Either
type constructor (partially applied, in this case, to String
), you can apply a function to a Right
value, but ignore the function if applied to a Left
value (your error). It provides a way of error propagation without having to check for the error.
fmap f (Right x) = Right (f x)
fmap f (Left y) = Left y
An applicative functor is similar, except the function itself can be wrapped just like the argument it is applied to. The <*>
operator unwraps both its operands, unlike fmap
which only unwraps its right operand.
Right f <*> Right x = Right (f x)
Left f <*> _ = Left f
_ <*> Left y = Left y
Typically, you don't wrap functions yourself: they result from using fmap
to partially apply a function to a wrapped value:
fmap (+) (Right 3) == Right (+ 3)
fmap (+) (Left "error") == Left "error"
So, when we are working with Either
values, the use of <$>
(infix fmap
) and <*>
let us pretend we are working with regular values, without worrying about whether they are wrapped with Left
or Right
. Right
values provide the expected Right
-wrapped answer, and Left
values are preserved. In the case of a binary operator, only the first Left
value is returned, but that is often sufficient.
(+) <$> Left "x undefined" <*> Left "y undefined" == Left "x undefined" <*> Left "y undefined"
== Left "x undefined"
(+) <$> Left "x undefined" <*> Right 9 == Left "x undefined" <*> Right 9
== Left "x undefined"
(+) <$> Right 3 <*> Left "y undefined" == Right (+ 3) <*> Left "y undefined"
== Left "y undefined"
(+) <$> Right 3 <*> Right 9 == Right (+3) <*> Right 9
== Right 12
In the end, using the Applicative
instance of Either String
lets us combine the results of evaluating two subexpressions without having to explicitly check if either recursive call of eval
actually succeeded. Successful recursive calls result in success; an error in either call is used as the same error for the top-level call.