-2

what are those operators in haskell? and <*> I have them in a line like this:

class Evaluable e where
    eval :: (Num a, Ord a) => (Ident -> Maybe a) -> (e a) -> (Either String a)
    typeCheck :: (Ident -> String) -> (e a) -> Bool

instance Evaluable NExpr where
    eval lookup (Plus left right) = (+) <$> eval lookup left <*> eval lookup right

3 Answers3

3

The <$> operator is an infix form of fmap. It allows you to apply a pure function to the value wrapped into some parametric type that belongs to a Functor class. The type of <$> is (a -> b) -> f a -> f b.

The <*> operator is quite similar to <$>. It allows you to apply a function wrapped into a parametric type to a value wrapped into the same parametric type. The type of <*> is f (a -> b) -> f a -> f b.

Graham
  • 7,431
  • 18
  • 59
  • 84
arrowd
  • 33,231
  • 8
  • 79
  • 110
3

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.

Community
  • 1
  • 1
chepner
  • 497,756
  • 71
  • 530
  • 681
1

In this particular case, it's a way to combine the results of eval. If one part of the the expression is failing, then the whole expression is failing.

This way, its possible to separate error handling of your application logic and to avoid complex nested case ... of.

To fully understand this, I'll advise do read on functors first, then applicative functors.

In parallel you can play with Maybe and Either, and write the equivalent code using case expressions.