Apply
Usage
Shorter: Same as Functor
, but the function is also in the box-like type, f
.
Longer:
Change a value, `a`,
that's currently stored in some box-like type, `f`,
into `b`
using a function, `(a -> b)`,
that is also stored in the same box-like type, `f`.
Definition
See its docs: Apply
class (Functor f) <= Apply f where
apply :: forall a b. f (a -> b) -> f a -> f b
infixl 4 apply as <*>
data Box a = Box a
instance Functor Box where
map :: forall a b. (a -> b) -> Box a -> Box b
map f (Box a) = Box (f a)
instance Apply Box where
apply :: forall a b. Box (a -> b) -> Box a -> Box b
apply (Box f ) (Box a) = Box (f a)
Put differently, Apply
solves a problem that occurs when using Functor
. If I have a function of type (a -> b -> c)
, I can use Functor
's map
/<$>
to lift that function into a Box-like type as before....
mapResult :: Box (Int -> Int)
mapResult = map (\first second -> first + second) (Box 1)
However, the resulting value stored in that Box-like type is a function. In other words, mapResult == Box (\second -> 1 + second)
. Functor
's map
only works if the function takes only one argument. If it takes 2+ arguments, map
will return a function stored in a Box
.
This is where Apply
comes to the rescue. We can continue to apply boxed arguments to that function until we eventually get a Box with a value in it:
finalResult :: Box Int
finalResult =
apply mapResult (Box 2) {-
...which is the same as...
Box ((\second -> 1 + second) 2)
Box ((\2 -> 1 + 2 ) )
Box (( 3 ) )
Box 3 -}
Thus, map
lifts functions that take n
-many arguments into a Box-like type, and Apply
's apply
/<*>
continues to pass n-1
-many boxed arguments into that function until the function executes.
Laws
Associative Composition
Definition: (<<<) <$> f <*> g <*> h == f <*> (g <*> h)
TODO: prove the above law using Box
(a lot of work, so ignoring for now...)
Derived Functions
- Do two computations, but only return...
- the first:
applyFirst
/<*
- the second:
applySecond
/*>
- the first:
liftN
is explained below:
LiftN Notation
Let's rename that Functor
's map
function to lift1
:
{-
map (\oneArg -> doStuffWith oneArg) (Box 4) -}
lift1 (\oneArg -> doStuffWith oneArg) (Box 4)
This function can only take one arg. What if want to take two args? We should call it lift2
:
lift2 (\arg1 arg2 -> andThen (doStuffWith arg1) arg2) (Box 4) (Box 4)
That works, but we could also write it:
(\arg1 arg2 -> andThen (doStuffWith arg1) arg2) <$> (Box 4) <*> (Box 4)
Using meta-language
function_NotInBox_takes_n_args <$> boxedArg1 <*> boxedArg2 -- <*> boxedArgN ...