arliss_obsidian/fp/Classes/Functor/Apply.md
Orion Kindel d74fc46755
update
2024-09-24 14:53:59 -05:00

2.0 KiB

Generalizes Functor#map to Functions with 2+ arguments; lifting functions a -> b -> c to f a -> f b -> f c

class Functor f <= Apply f where
  apply :: forall a b. f (a -> b) -> f a -> f b

infixl apply as <*>

Tip

Apply often replaces the pattern of "building towards a final result"

Consider a function that Tuples together its arguments

mk3Tuple a b c = a /\ b /\ c

A common situation is wanting to build something like this 3-tuple from some components that are trapped in a context like Maybe or Either, and we want to say "when all these things are ok, build it"; this is exactly what Apply lets us do:

maybeMk3Tuple :: Maybe a -> Maybe b -> Maybe c -> Maybe (a /\ b /\ c)
maybeMk3Tuple ma mb mc = Just mk3Tuple <*> ma <*> mb <*> mc

maybeMk3Tuple (Just 1) (Just 2) (Just 3)
-- Just (1 /\ 2 /\ 3)

maybeMk3Tuple (Just "a") (Just 3) (Nothing)
-- Nothing

maybeMk3Tuple Nothing Nothing Nothing
-- Nothing

Tip

Obscure but occasionally usefully, Apply in collections lets us apply multiple functions to each element:

[(_ * 10), (_ * 20)] <*> [1, 2, 3]
-- [10, 20, 20, 40, 30, 60]

[identity, const ", "] <*> ["a", "b", "c"]
-- ["a", ",", "b", ",", "c", ","]

Examples

lift (a + b) / c to Maybe

f :: Number -> Number -> Number
f a b c = (a + b) / c

mf :: Maybe Number -> Maybe Number -> Maybe Number
mf ma mb mc = pure f <*> ma <*> mb <*> mc

Build up a Person Record from Maybe fields; short-circuiting to Nothing if any fields are Nothing

type PartialPerson =
  { name :: Maybe String
  , email :: Maybe String
  , password :: Maybe String
  }

type Person =
  { name :: String
  , email :: String
  , password :: String
  }

person :: PartialPerson -> Person
person
  { name: name'
  , email: email'
  , password: password'
  } =
  let
    buildPerson name email password = {name, email, password}
  in
    Just buildPerson <*> name' <*> email' <*> password'