Applicative
A functor with pure : a -> f a and a way to combine independent computations, <*> : f (a -> b) -> f a -> f b, or equivalently product : f a -> f b -> f (a * b). Every monad is applicative, but applicatives are strictly more general: because later steps cannot depend on earlier results, effects can be accumulated, parallelised or inspected before running.
The laws, for and (written <*>):
An equivalent presentation replaces by a product, which makes the structure clearer: an applicative is a lax monoidal functor, with
associative and unital up to the isomorphisms and . The two are interdefinable: and .
Compared with a monad's , the product takes both computations up front. The second cannot depend on the first's result, and that restriction is what the next example relies on.
Validation. OCaml's let+ and and+ are exactly map and product, so an applicative gets its own syntax.
(* Validation: like result, but when both sides fail, both errors are kept.That only works because the second computation does not depend on thefirst one's result: it is applicative, not monadic. *)type 'a validation = Ok of 'a | Errors of string listlet map f = function Ok x -> Ok (f x) | Errors e -> Errors elet product a b =match (a, b) with| Ok x, Ok y -> Ok (x, y)| Errors e, Ok _ | Ok _, Errors e -> Errors e| Errors e1, Errors e2 -> Errors (e1 @ e2)let ( let+ ) x f = map f xlet ( and+ ) = productlet nonempty field s = if s = "" then Errors [ field ^ " is empty" ] else Ok slet age s =match int_of_string_opt s with| Some n when n >= 0 -> Ok n| _ -> Errors [ "age is not a natural number" ]type person = { name : string; email : string; age : int }let person ~name ~email ~age:a =let+ name = nonempty "name" nameand+ email = nonempty "email" emailand+ age = age a in{ name; email; age }
Running it. All three errors are reported, not just the first.
Ok { name = "Ada"; email = "[email protected]"; age = 36 }Errors ["name is empty"; "email is empty"; "age is not a natural number"]
A monad could not do this lawfully: with the second check is a function of the first result, which does not exist when the first check failed, so it has to stop there. The product has both sides and can keep both sides' errors.
The same restriction makes applicatives analysable. A parser built only from and has a fixed structure that can be inspected, optimised or printed as a grammar before any input is read, and a batch of independent requests can be sent at once instead of one after another.
see also
- FunctorA type constructor f with a map operation, fmap : (a -> b) -> f a -> f b, that preserves identity and composition. Lists, options, trees and functions out of a fixed type are functors. In OCaml the word also means something else: a module parametrised by another module, such as Map.Make.
- MonadIn functional programming, a monad is a type constructor m with two operations, return : a -> m a and bind : m a -> (a -> m b) -> m b, satisfying three laws. It lets code with some extra behaviour, such as failure, several results, configuration, state or I/O, be written as a sequence of ordinary steps, with the behaviour defined once in bind. The notion comes from category theory.
- Parser combinatorA parser is a function from input to a result and the rest of the input, or failure. Parser combinators build parsers from smaller ones: sequencing, choice, repetition. A grammar becomes a set of ordinary recursive definitions in the host language.
further reading
- [1]C. McBride, R. Paterson, “Applicative programming with effects”, Journal of Functional Programming 18 (2008).
- [2]The OCaml manual, “Binding operators”.