Peter Hall
Peter Hall

Reputation: 58705

Can someone explain where Applicative instances arise in this code?

isAlphaNum :: Char -> Bool
isAlphaNum = (||) <$> isAlpha <*> isNum 

I can see that it works, but I don't understand where the instances of Applicative (or Functor) come from.

Upvotes: 17

Views: 881

Answers (3)

C. A. McCann
C. A. McCann

Reputation: 77374

This is the Applicative instance for ((->) r), functions from a common type. It combines functions with the same first argument type into a single function by duplicating a single argument to use for all of them. (<$>) is function composition, pure is const, and here's what (<*>) translates to:

s :: (r -> a -> b) -> (r -> a) -> r -> b
s f g x = f x (g x)

This function is perhaps better known as the S combinator.

The ((->) r) functor is also the Reader monad, where the shared argument is the "environment" value, e.g.:

newtype Reader r a = Reader (r -> a)

I wouldn't say it's common to do this for the sake of making functions point-free, but in some cases it can actually improve clarity once you're used to the idiom. The example you gave, for instance, I can read very easily as meaning "is a character a letter or number".

Upvotes: 18

Landei
Landei

Reputation: 54574

It should be noted that you get a similar effect by using the lift functions, e.g.:

import Data.Char
import Control.Applicative 

isAlphaNum = liftA2 (||) isAlpha isNumber

Or, using the monad instance of ((->) r) instead of the applicative one:

import Data.Char
import Control.Monad 

isAlphaNum = liftM2 (||) isAlpha isNumber

[Digression]

Now that you know how to distribute one argument to two intermediate functions and the result to a binary function, there is the somehow related case that you want to distribute two arguments to one intermediate function and the results to a binary function:

import Data.Function

orFst = (||) `on` fst

-- orFst (True,3) (False, 7)
--> True

This pattern is e.g. often used for the compare function.

Upvotes: 3

gatoatigrado
gatoatigrado

Reputation: 16850

You get instances of what are called static arrows (see "Applicative Programming with Effects" by Conor McBride et al.) for free from the Control.Applicative package. So, any source type, in your case Char, gives rise to an Applicative instance where any other type a is mapped to the type Char -> a.

When you combine any of these, say apply a function f :: Char -> a -> b to a value x :: Char -> a, the semantic is that you create a new function Char -> b, which will feed its argument into both f and x like so,

f <*> x = \c -> (f c) (x c)

Hence, as you point out, this makes your example equivalent to

isAlphaNum c = (isAlpha c) || (isNum c)

In my opinion, such effort is not always necessary, and it would look nicer if Haskell had better syntactic support for applicatives (maybe something like 2-level languages).

Upvotes: 7

Related Questions