On this page:
1.1 Functors
gen:  functor
functor?
functor/  c
map
1.1.1 Implementing new functors
1.2 Applicatives
gen:  applicative
applicative?
applicative/  c
pure
pure?
pure/  c
1.2.1 Implementing new applicative functors
1.3 Monads
gen:  monad
monad?
monad/  c
chain
do
<-
join
map/  m
1.3.1 Implementing new monads

1 InterfacesšŸ”—ā„¹

1.1 FunctorsšŸ”—ā„¹

 (require data/functor) package: functional-lib

A functor can be thought of as a kind of “container”. This can be something like a list or hash map, which actually contains values, or something like a channel, which produces values over time. All functors work with map, which allows producing a new functor with the elements “contained” by the functor modified by the mapping function.

For example, using map on lists simply modifies each element of the list, just like map from racket/base.

> (map add1 '(1 2 3))

#<stream>

However, unlike map from racket/base, this more generic map can also map over things like optional values.

> (map add1 (just 2))

(just 3)

> (map add1 nothing)

#<nothing>

Functors provide a way to manipulate data in a consistent way without needing to know the data’s underlying structure. To ensure consistency and predictability, all implementations of gen:functor must conform to the functor laws, of which there are two:

  1. (map identity x) must be equivalent to x.

  2. (map (compose f g) x) must be equivalent to (map f (map g x)).

Most reasonable definitions of a functor will satisfy these laws already, but it is possible to write an implementation that does not, and there is no guarantee that functions in this library will work predictably on unlawful functors.

interface

gen:functor

procedure

(functor? v)  boolean?

  v : any/c

value

functor/c : contract?

The generic interface that specifies functors.

procedure

(map f x)  functor?

  f : procedure?
  x : functor?
Applies f to the functor x.

1.1.1 Implementing new functorsšŸ”—ā„¹

To define your own functors, simply implement the gen:functor generic interface and implement the map method. The only implementation requirements are that methods conform to their associated contracts and that they follow the functor laws.

Here is an example implementation of the most trivial possible functor, the identity functor:

> (struct id (val)
    #:transparent
    #:methods gen:functor
    [(define (map f x)
       (id (f (id-val x))))])
> (map add1 (id 12))

(id 13)

1.2 ApplicativesšŸ”—ā„¹

 (require data/applicative) package: functional-lib

Applicative functors generalize function application to work with any kind of data structure, not just procedures. This is much like prop:procedure, but it is specified via a generic interface. Additionally, all implementations of gen:applicative should also implement gen:functor.

> ((just +) (just 1) (just 2))

(just 3)

> ((just +) nothing (just 2))

#<nothing>

> (sequence->list
   ((list + *) (list 3 4) (list 10 20)))

'(13 23 14 24 30 60 40 80)

In addition to the implementation of apply, the gen:applicative interface must also implement a function called pure. This function “lifts” an ordinary value into the functor. For example, the pure function for lists is just list, but the pure function for optional values is just.

Like functors, applicative functors have their own set of applicative functor laws which all implementations of gen:applicative must conform to:

  1. ((pure identity) x) must be equivalent to x.

  2. (((pure compose) f g) x) must be equivalent to (f (g x)).

  3. ((pure f) (pure x)) must be equivalent to (pure (f x)).

Most reasonable definitions of an applicative functor will satisfy these laws already, but it is possible to write an implementation that does not, and there is no guarantee that functions in this library will work predictably on unlawful applicative functors.

interface

gen:applicative

procedure

(applicative? v)  boolean?

  v : any/c

value

applicative/c : contract?

procedure

(pure v)  applicative?

  v : any/c
Lifts a plain value into an applicative functor. When initially called, this function simply places the value in a box because it cannot yet know what kind of functor it needs to produce. When used, the value will be coerced into a functor of the appropriate type using the relevant value’s pure method.

procedure

(pure? v)  boolean?

  v : any/c
A predicate that determines if a value is a boxed value that is awaiting coercion into a concrete type of applicative functor. Ideally, you should never need to use this function, but sometimes values cannot be immediately coerced, so this can be needed.

procedure

(pure/c val-ctc)  contract?

  val-ctc : contract?
A contract that accepts boxed values awaiting coercion into a concrete type of applicative functor. Ideally, you should never need to use this function, but sometimes values cannot be immediately coerced, so this can be needed.

1.2.1 Implementing new applicative functorsšŸ”—ā„¹

Implementing your own applicative functors is somewhat more complicated than implementing plain functors. You must implement two methods, named pure and apply. The former, unlike the pure function exported by data/applicative, should be a function of two arguments, the first of which should be ignored. This is necessary in order to properly perform dynamic dispatch with racket/generic, since some value must exist to be dispatched on. The first argument is therefore the value being used for dispatch, but there is no guarantee about what it is, so you should always ignore it completely.

Implementing the apply method is somewhat more straightforward. It should be a function of two arguments, this first corresponding to the functor in application position and second a list of functors provided as arguments in the application. It should produce an applicative functor result.

Here is an example implementation of the most trivial possible applicative functor, the identity functor:

> (require (prefix-in base: racket/base))
> (struct id (val)
    #:transparent
    #:methods gen:functor
    [(define (map f x)
       (id (f (id-val x))))]
    #:methods gen:applicative
    [(define (pure _ x)
       (id x))
     (define (apply f xs)
       (id (base:apply (id-val f) (base:map id-val xs))))])
> ((id +) (pure 2) (pure 3))

(id 5)

1.3 MonadsšŸ”—ā„¹

 (require data/monad) package: functional-lib

A monad is a mechanism for sequencing pure values in a context. Monads are an extremely general concept that are notoriously difficult to explain (despite being relatively simple once you understand them), and I will not attempt to explain them here (though perhaps I will try someday).

All monads must also be applicative functors, but they add one more method, called chain. The chain method, much like map, applies a function to a value in a context, but unlike map, the applied function must produce a new monad, not a pure value. Monads can be used to control sequencing of computation in a very flexible way.

Using the chain function directly can become tedious and hard to read beyond a couple of nested applications, so the do form is provided to make sequencing monadic operations more pleasant to read and write.

Like functors and applicative functors, monads have their own set of monad laws which all implementations of gen:monad must conform to:

  1. (chain f (pure x)) must be equivalent to (f x).

  2. (chain pure x) must be equivalent to x.

  3. (chain (λ (y) (chain g (f y))) x) must be equivalent to (chain g (chain f x)).

Most reasonable definitions of a monad will satisfy these laws already, but it is possible to write an implementation that does not, and there is no guarantee that functions in this library will work predictably on unlawful monads.

interface

gen:monad

procedure

(monad? v)  boolean?

  v : any/c

value

monad/c : contract?

The generic interface that specifies monads.

procedure

(chain f x)  monad?

  f : (any/c . -> . monad?)
  x : monad?
Applies f to the value within the monadic context x and produces a new monad as the result.

syntax

(do expr-or-clauses)

 
expr-or-clauses = monad-expr
  | do-clause expr-or-clauses
     
do-clause = [match-pattern <- monad-expr]
  | monad-expr
  | internal-definition
 
  monad-expr : monad?
Syntactic shorthand for successive, nested uses of chain. The do form allows writing arbitrary sequential monadic operations without an excessive proliferation of lambdas and a significant amount of rightward drift.

In its simplest form, do does nothing at all. Any do block containing only a single expression is equivalent to the expression itself.

> (do 3)

3

> (do "hello, world")

"hello, world"

> (do '(1 2 3 4))

'(1 2 3 4)

This is obviously not particularly useful, but do becomes helpful when using multiple sub-forms. Each do-clause may bind the result of a chain operation, which may be used in subsequent computations.

> (sequence->list
   (do [x <- '(1 2 3)]
       (pure (* x 2))))

'(2 4 6)

Specifically, any block of the form (do [x <- m] clause ...+) is precisely equivalent to (chain (λ (x) (do clause ...+)) m). More generally, the binding identifier can be replaced with a match pattern, in which case the resulting code uses match-lambda instead.

Not every chain operation has a useful result. In that case, the binding brackets may be omitted, simply leaving the monad-expr. In this case, a chain call will still be produced, but the result will not be bound anywhere.

> (sequence->list
   (do '(1 2 3)
       (pure 'hello)))

'(hello hello hello)

Finally, arbitrary internal definitions may be interspersed between each do-clause. These definitions do not produce new chain calls, they simply create new bindings.

If a macro used within a do block produces a begin form containing both internal definitions and expressions, the whole form is spliced into the surrounding internal definition context. All expressions will be simply evaluated for side-effects and will not result in any additional calls to chain.

> (sequence->list
   (do [x <- '(1 2)]
       (define y (* x 2))
       [z <- '(a b)]
       (define (prettify a b) (~a a ": " b))
       (pure (prettify y z))))

'("2: a" "2: b" "4: a" "4: b")

Internal definitions defined within do blocks may refer to all previous bindings, but not subsequent ones. However, multiple internal definitions directly next to one another may be mutually recursive, so long as they are not separated by a chain.

> (do [x <- (just 7)]
      (define (calls-b)
        (add1 (b)))
      (define (b)
        (- x))
      [y <- (just (calls-b))]
      (pure (* 2 y)))

(just -12)

syntax

<-

syntax

Recognized specially within forms like do. Using either form as an expression is a syntax error.

procedure

(join x)  monad?

  x : monad?
Joins a nested monadic value (a monadic value embedded within another monadic value, both of the same type) into a single value. In other words, this flattens a monadic value by a single layer.

> (sequence->list (join '((1 2) (3 4))))

'(1 2 3 4)

> (sequence->list (join '()))

'()

> (join (just (just 'hello)))

(just 'hello)

> (join (just nothing))

#<nothing>

> (join nothing)

#<nothing>

procedure

(map/m f xs)  monad?

  f : (any/c . -> . monad?)
  xs : sequence?
Applies f to each element of xs, then chains the resulting monadic values from left to right and returns the results as a single monadic value.

> (define (ascii->char x)
    (if (<= 0 x 127)
        (just (integer->char x))
        nothing))
> (map/m ascii->char '(76 33))

(just '(#\L #\!))

> (map/m ascii->char '(76 -5 33))

#<nothing>

1.3.1 Implementing new monadsšŸ”—ā„¹

Implementing your own monads is no more complicated than implementing your own applicative functors, you just need to provide an implementation of chain that satisfies the monad laws.

Here is an example implementation of the most trivial possible monad, the identity monad:

> (require (prefix-in base: racket/base))
> (struct id (val)
    #:transparent
    #:methods gen:functor
    [(define (map f x)
       (id (f (id-val x))))]
    #:methods gen:applicative
    [(define (pure _ x)
       (id x))
     (define (apply f xs)
       (id (base:apply (id-val f) (base:map id-val xs))))]
    #:methods gen:monad
    [(define (chain f x)
       (f (id-val x)))])
> (do [x <- (id 1)]
      [y <- (id 2)]
      (pure (+ x y)))

(id 3)