In Real World Haskell, they describe combinators like this:
In Haskell, we refer to functions that take other functions as arguments and return new functions as combinators.
And then later they state that maybeIO
function is a combinator and its type signature looks like this:
maybeIO :: IO a -> IO (Maybe a)
But all I can see is that maybeIO
is a function that takes a value wrapped in IO monad and returns a value in IO monad. Then how does this function become a combinator ?
There are really 2 things that we could mean when we say combinator. The word is a bit overloaded.
We usually mean a function which "combines" things. For example your function takes in an IO
value and builds up a more complex value. Using these "combinators" we can combine and create new complex IO
values from relatively few primitive functions to create IO
values.
For example, rather than creating a function which reads 10 files, we use mapM_ readFile
. Here combinators are functions that we use to combine and build values
The stricter computer sciencey term is a "function with no free variables". So
-- The primitive combinators from a famous calculus, SKI calculus.
id a = a -- Not technically primitive, genApp const const
const a b = a
genApp x y z = x z (y z)
This is part of a grander field called "Combinatory logic" in which you seek to essentially eliminate free variables and replace it with combinators and a few primitive functions.
TLDR: usually when we say combinator, we refer to a more general notion called the "combinator pattern" where we have a handful of primitive functions and a lot of user-defined functions to build up more complex values.