This SRFI defines two disjoint immutable container types known as Maybe and Either, both of which can contain objects collectively known as their payload. A Maybe object is either a Just object or the unique object Nothing (which has no payload); an Either object is either a Right object or a Left object. Maybe represents the concept of optional values; Either represents the concept of values which are either correct (Right) or errors (Left).
Note that the terms Maybe, Just, Nothing, Either, Right, and Left are capitalized in this SRFI so as not to be confused with their ordinary use as English words. Thus "returns Nothing" means "returns the unique Nothing object"; "returns nothing" could be interpreted as "returns no values" or "returns an unspecified value".
- SRFI-189: Maybe and Either
- SRFI Description
- Maybe and Either
- About This Egg
This page includes excerpts from the SRFI document, but is primarily intended to document the forms exported by this egg. For a full description of the SRFI, see the full SRFI document.
John Cowan (text) and Wolfgang Corcoran-Mathe (sample implementation)
The four types Just, Nothing, Left, and Right are disjoint from each other and from all other Scheme types.
We speak of unwrapping a container when we extract its payload, and wrapping values in a container when we create the container with the values as its payload.
The following names are used for the arguments:
|obj, default||Any Scheme object.|
|maybe||A Maybe object.|
|either||An Either object.|
|failure||A procedure that accepts zero arguments (unless specified otherwise).|
|success||A procedure that accepts one or more arguments.|
|pred||A predicate that accepts zero or more arguments.|
|equal||A predicate that accepts two arguments and returns a boolean value. It is not necessarily an equivalence relation.|
|proc||A procedure that accepts zero or more arguments and returns zero or more values.|
|mproc||A procedure that accepts zero or more arguments and returns zero or more values wrapped in a single Maybe or Either container.|
|list||A Scheme list.|
|producer||A procedure that accepts no arguments and returns some number of values.|
If the procedures are passed arguments that do not have the type implied by the argument names, then a condition of kind (exn type assertion) is raised.
- just obj ...procedure
Monadic pure. Returns the objs wrapped in a Just.
Returns the unique Nothing object.
- right obj ...procedure
Monadic pure. Returns the objs wrapped in a Right.
- left obj ...procedure
Returns the objs wrapped in a Left.
- list->just listprocedure
- list->left listprocedure
- list->right listprocedure
Returns a Just / Left / Right whose values are the elements of list. Note that these procedures are not monadic; they are equivalent to just/left/right but accept a list rather than multiple arguments. There is no need for list->nothing distinct from nothing.
- maybe->either maybe obj ...procedure
If maybe is a Just, returns a Right with the same payload objects in the sense of eqv?; otherwise returns a Left of objs.
- either->maybe eitherprocedure
If either is a Right, returns a Just with the same payload objects in the sense of eqv?; otherwise returns Nothing.
- either-swap eitherprocedure
If either is a Left, return a Right with the same payload objects (in the sense of eqv?), and vice versa.
- just? objprocedure
- nothing? objprocedure
- right? objprocedure
- left? objprocedure
Returns #t if obj is a Just, Nothing, Left, or Right respectively, and #f otherwise.
- maybe? objprocedure
Returns #t if obj is a Maybe (that is, either a Just or Nothing) and #f otherwise.
- either? objprocedure
Returns #t if obj is an Either (that is, either a Right or a Left) and #f otherwise.
- maybe= equal maybe1 maybe2 ...procedure
Returns #t if all maybes are Nothing, or if they all are Justs and their respective payload objects are the same in the sense of equal, and #f otherwise.
- either= equal either1 either2 ...procedure
Returns #t if all eithers are all Lefts or all Rights and their respective payload objects are the same in the sense of equal, and #f otherwise.
- maybe-ref maybe failure #!optional successprocedure
If maybe is a Just, tail-calls the procedure success on the values of its payload and returns the result. Otherwise, it tail-calls the procedure failure on no arguments and returns the result. The default value of success is values.
- either-ref either failure #!optional successprocedure
If either is a Right, tail-calls the procedure success on the values of its payload and returns the result. Otherwise, it tail-calls the procedure failure on the values of its payload and returns the result. The default value of success is values.
- maybe-ref/default maybe default ...procedure
- either-ref/default either default ...procedure
If maybe/either is a Just/Right, returns the values of its payload; otherwise returns the defaults as multiple values.
- maybe-join maybeprocedure
- either-join eitherprocedure
Monadic join. If maybe/either is a Just/Right whose single payload object is a Maybe/Either, returns that payload; if it is a Nothing/Left, returns maybe/either. In all other cases (including a Just/Right with multiple values) it is an error; specifically, a condition of kind (exn type payload) is raised. Thus (maybe-join (just (just x)) returns Just x and (maybe-join (just (nothing)) returns Nothing, and similarly for either-join.
- maybe-compose mproc1 mproc2 ...procedure
- either-compose mproc1 mproc2 ...procedure
Returns an mproc that accepts zero or more arguments and applies the following iterative algorithm to each mproc:
The mproc is applied to the arguments and returns a Maybe/Either interim result. If the result is Nothing / a Left, it is returned as the final result. If it is a Just/Right, the next mproc is applied to the values of its payload, returning the next interim result. When no more mprocs are available, the interim result is returned as the final result.
It is an error if one of the mprocs does not accept as arguments the number of values wrapped in the Just/Right produced by its predecessor.
- maybe-bind maybe mproc1 mproc2 ...procedure
- either-bind either mproc1 mproc2 ...procedure
Monadic bind. If maybe/either is a Just/Right, it behaves as if maybe-compose/either-compose were applied to mprocs, and the resulting mproc applied to the payload of maybe/either. But if maybe/either is a Nothing/Left, it is immediately returned.
These procedures treat Maybes (and in some cases Eithers) as sequences of length 0 or 1. This length depends only on the type of the container and not on the number of objects it contains.
- maybe-length maybeprocedure
- either-length eitherprocedure
Return 1 if maybe/either is a Just/Right, and 0 otherwise.
- maybe-filter pred maybeprocedure
- maybe-remove pred maybeprocedure
- either-filter pred either obj ...procedure
- either-remove pred either obj ...procedure
If maybe/either is a Just/Right and the values of its payload satisfy / do not satisfy pred, then return maybe; otherwise, returns Nothing / a Left of objs.
- maybe-sequence mappable map #!optional aggregatorprocedure
- either-sequence mappable map #!optional aggregatorprocedure
The purpose of these procedures is to transform a collection of Maybes/Eithers of some particular type into a Maybe/Either of a collection, often of the same type.
The mappable argument is the collection to be transformed, and the map argument is the appropriate procedure that can transform it, one element at a time. The signature of map is (map proc mappable), where proc is supplied by the implementation. Typical mappables are lists and vectors, and map and vector-map are useful map functions, though not the only possible ones.
Each element is processed as follows: If it is a Just/Right, its values are unwrapped and the aggregator function (which defaults to list) is applied to them. But if it is a Left/Nothing, that object is immediately returned as the value of maybe-sequence/either-sequence.
Assuming there are no Lefts/Nothings, the map function is then responsible for constructing the new mappable out of the results of the calls on aggregator.
For example, a list of Justs becomes a Just list of lists if the map procedure is map and the aggregator is list. In the same way, a vector of Rights containing one value each becomes a Right vector if the map procedure is vector-map and the aggregator is (lambda (x) x).
The purpose of Maybe and Either is to provide first-class objects representing the distinction between success and failure in such a way that any number of success values (and, in the case of Either, any number of failure values) can be packaged up in a single object without reserving any values to indicate success or failure. A variety of protocols are already in use in Scheme and other Lisps to more or less perfectly represent the distinction. Some reserve a value to represent failure which cannot be returned as a success value; some handle only one success value; some are not first-class.
In order to facilitate communication between code using Maybe/Either and code using another protocol, the following procedures convert between Maybe/Either objects and some of the most usual protocols. Conversion in either direction is frequently lossy.
☞ The following procedures interface between the Maybe protocol and the list protocol, which uses an empty list to represent failure and a non-empty list to represent success.
- maybe->list maybeprocedure
- either->list eitherprocedure
Returns a list whose elements are the payload of maybe/either; if maybe/either is Nothing/Left, returns the empty list. It is an error to mutate the result of this procedure, though this implementation can't enforce this requirement.
- list->maybe listprocedure
- list->either list obj ...procedure
If list is empty, returns Nothing / wraps objs in a Left and returns it; otherwise, wraps the list elements in a Just/Right and returns that.
☞ The following procedures interface between the Maybe and Either protocols and the truth protocol, which uses #f to represent failure and a single true value to represent success.
- maybe->truth maybeprocedure
- either->truth eitherprocedure
If maybe/either is a Just/Right, returns its payload; otherwise returns #f. If the Just/Right does not have exactly one value, a condition of kind (exn type payload) is raised.
- truth->maybe objprocedure
- truth->either obj fail-obj ...procedure
If obj is #f, return Nothing / a Left of fail-objs; otherwise, return a Just/Right whose payload is obj.
☞ The following procedures interface between the Maybe protocol and the list-truth protocol, which uses #f to represent failure and a list to represent success.
- maybe->list-truth maybeprocedure
- either->list-truth eitherprocedure
If maybe/either is a Just/Right, returns a list whose elements are the payload; otherwise, returns #f. It is an error to mutate the result of this procedure, though this implementation can't enforce this requirement.
- list-truth->maybe list-or-falseprocedure
- list-truth->either list-or-false obj ...procedure
If list-or-false is #f, returns Nothing / a Left of objs; otherwise, wraps the list elements in a Just/Right and returns it.
☞ The following procedures interface between the Maybe and Either protocols and the generation protocol, which uses an end-of-file object to represent failure and any other value to represent success.
- maybe->generation maybeprocedure
- either->generation eitherprocedure
If maybe/either is a Just/Right, then its payload is returned. If the Just/Right does not have exactly one value, then a condition of kind (exn type payload) is raised. If maybe/either is Nothing / a Left, then an end-of-file object is returned.
- generation->maybe objprocedure
- generation->either obj fail-objs ...procedure
If obj is an end-of-file object, return Nothing / a Left of fail-objs. Otherwise, return obj wrapped in a Just/Right.
☞ The following procedures interface between the Maybe and Either protocols and the values protocol, which returns one or more values to represent success and zero values to represent failure.
- maybe->values maybeprocedure
- either->values eitherprocedure
If maybe/either is a Just/Right, returns the values of its payload; otherwise returns no values.
- values->maybe producerprocedure
- values->either producer fail-obj ...procedure
These procedures invoke producer with no arguments. If no values are returned, Nothing / Left of fail-objs is returned. Otherwise the values are wrapped in a Just/Right and returned.
☞ The following procedures interface between the Maybe protocol and the two-values protocol, which returns #f and #f to represent failure and a single value and #t to represent success. (This protocol is more often used in Common Lisp, where additional values are automatically discarded if the continuation expects only one.)
- maybe->two-values maybeprocedure
If maybe is a Just, returns the value of its payload and the additional value #t; otherwise returns two values, both #f. If maybe does not have exactly one value, then a condition of kind (exn type payload) is raised.
- two-values->maybe producerprocedure
This procedure is the inverse of maybe->two-values. It invokes producer with no arguments. If the second value is true, the first value is wrapped in a Just and returned; otherwise, Nothing is returned.
- exception->either pred thunkprocedure
This procedure is in a sense the inverse of maybe-ref. A call to thunk is made, wrapped in an exception handler that examines any exception signaled during thunk's execution. If the condition object satisfies pred, it is wrapped in a Left which is then returned; if it does not, the exception is reraised. But if no exception is raised, the values returned by thunk are wrapped in a Right and the Right is returned.
- maybe-map proc maybeprocedure
- either-map proc eitherprocedure
Monadic map. If maybe/either is a Just/Right, applies proc to the payload values and wraps the returned values as a Just/Right; otherwise returns maybe/either.
- maybe-for-each proc maybeprocedure
- either-for-each proc eitherprocedure
If maybe/either is a Just/Right, applies proc to the payload values and discards the result; otherwise does nothing. Returns an unspecified result.
- maybe-fold kons nil maybeprocedure
- either-fold kons nil eitherprocedure
If maybe/either is a Just/Right, kons is invoked on the values of its payload and the additional value nil and the result is returned; otherwise, nil is returned.
- maybe-unfold stop? mapper successor seed ...procedure
- either-unfold stop? mapper successor seed ...procedure
If stop? returns true on seeds, a Nothing / a Left of seeds is returned. Otherwise, successor is applied to seeds. If stop? returns false on the results of successor, it is an error. (This is a condition of kind (exn assertion).) But if the second call to stop? returns true, mapper is applied to seeds and the results are wrapped in a Just/Right and returned.
- (maybe-if maybe-expr just-expr nothing-expr)syntax
Equivalent to (if (just? maybe-expr) just-expr nothing-expr), except that a condition of kind (exn type assertion) is raised if maybe-expr does not evaluate to a Maybe.
- (maybe-and expr ...)syntax
- (either-and expr ...)syntax
The exprs are evaluated in order. If an expr evaluates to a Nothing / Left, it is returned without evaluating any more expressions. Otherwise, the last Just / Right is returned. If there are no exprs, then a Just/Right of an unspecified value is returned. If an expr does not evaluate to a Maybe/Either, a condition of kind (exn type assertion) is raised.
- (maybe-or expr ...)syntax
- (either-or expr ...)syntax
The exprs are evaluated in order. If an expr evaluates to a Just/Right, it is returned without evaluating any more expressions. Otherwise, the last Nothing/Left is returned. If there are no exprs, then Nothing / a Left of an unspecified value is returned. If an expr does not evaluate to a Maybe/Either, a condition of kind (exn type assertion) is raised.
- (maybe-let* (claw ...) . body)syntax
- (either-let* (claw ...) . body)syntax
The Maybe/Either equivalent of and-let* from SRFI 2. However, instead of testing whether a claw is true or false, it tests whether it is Just/Right or Nothing/Left. If a claw does not evaluate to a Maybe/Either, an condition of kind (exn type assertion) is raised. If a claw evaluates to a Just or Right that wraps zero values or more than one value, then a condition of kind (exn type payload) is raised.
A claw is either a bound identifier, or a list of length 1, in which case it is an expression, or a list of length 2, in which case it is a variable and an expression. In either case, the value of the claw is the value of the bound variable or expression. If the value of a claw is Nothing / a Left, the expression immediately returns that value. But if the value of a claw is a Just/Right, then the variable (if any) is bound to its unwrapped value; the scope of this binding is any remaining claws and the body.
If the values of all the claws are Just/Right, then the body is evaluated as if it were in a (let () ...), and its values are wrapped in a Just/Right and returned.
- (maybe-let*-values (claw ...) . body)syntax
- (either-let*-values (claw ...) . body)syntax
The same as maybe/either-let*, except that the first element of a two-element claw is a <formals> from lambda, and the variables mentioned in it (if any) are bound to the values that result from unpacking a Just/Right. Note that a claw with a single variable in maybe/either-let* is bound to the sole value contained in the Just/Right, whereas in maybe/either-let*-values, it is bound to a list of all the values, just as in a lambda expression, a <formals> consisting of a single variable is bound to a list of all the procedure arguments.
- (either-guard pred-expr . body)syntax
The syntactic analogue of exception->either. The body is evaluated, and the values it produces are wrapped in a Right and returned, unless an exception occurs. If the condition object that is raised satisfies the predicate that is the value of pred-expr, the condition object is wrapped in a Left and returned; otherwise the condition is reraised as if by raise-continuable.
These procedures provide trivalent logic in the style of SQL on Maybe objects containing a single value. For the purposes of this section, an object counts as false if it is Just #f, true if it is Just anything else. It is an error if any argument is not a Maybe.
Unlike and and or, tri-and and tri-or are procedures and not syntax, because determining their value requires that all the arguments be evaluated in order to provide correct SQL-style semantics. For example, (and #f (nothing)) will return #f immediately without evaluating its second argument, but (tri-and (just #f) (nothing)) will return Nothing.
- tri-not maybeprocedure
Returns Just #t if maybe is false, Just #f if maybe is true, and Nothing if maybe is Nothing.
- tri=? maybe1 maybe2 ...procedure
Similar to boolean=?, returning Just #t if all the maybes are true or if all are false. Otherwise, if any maybe is Nothing or any two maybes have different (trivalent) truth values, returns Just #f.
Note: this function is not transitive and therefore is not an equivalence relation.
- tri-and maybe ...procedure
If all maybes are true, Just #t is returned. If any maybe is false or Nothing, then the first such maybe is returned. If there are no arguments, Just #t is returned.
- tri-or maybe ...procedure
If all maybes are false, Just #f is returned. If any maybe is true or Nothing, then the first such maybe is returned. If there are no arguments, Just #f is returned.
- tri-merge maybe ...procedure
If any maybes are true or false, then the first such maybe is returned. If all maybes are Nothing, then Nothing is returned. If there are no arguments, Nothing is returned.
Here is a variant of SRFI 1 find that returns a Maybe, and so can be safely used to search for #f in a list, just like any other value:
(define (maybe-find pred list) (cond ((null? list) (nothing)) ((pred (car list)) (just (car list))) (else (maybe-find pred (cdr list)))))
The following examples show how find and maybe-find differ in their results:
(define (atom? obj) (not (pair? obj))) (define with-t '((1) #t (3) 4 5)) (define with-f '((1) #f (3) 4 5)) (define without '((1) (2) (3) (4) (5))) (find atom? with-t) => #t (find atom? with-f) => #f (find atom? without) => #f (maybe-find atom? with-t) => Just #t (maybe-find atom? with-f) => Just #f (maybe-find atom? without) => Nothing
And here is head, which is more general than car because it lets the caller figure out what happens if its argument is not a pair and can handle an argument that is either an actual pair or Just a pair.
(define (head x) (cond ((pair? x) (just (car x))) ((and (just? x) (pair? (maybe-ref x))) (just (car (maybe-ref x)))) (else (nothing))))
In other words, if the argument to head is a pair, whether wrapped in a Just or not, the result is the car of that pair wrapped in a Just; in all other cases, the result is Nothing. (There are simpler ways to do this using more advanced procedures.)
This egg tries to provide useful information when things go wrong. Type errors raise conditions of kind (exn type assertion), while arity mismatches cause (exn arity assertion) conditions to be raised. Whenever the payload of a Maybe/Either violates a requirement (e.g. that of maybe-join), a condition of kind (exn type payload) is raised.
See Module (chicken condition) for more information.
The SRFI requires that the forms in the Syntax section signal an error that satisfies error-object?; this egg raises more detailed exceptions. This is fine, since CHICKEN's error-object? (in r7rs) is simply an alias for condition?.
Wolfgang Corcoran-Mathe <wcm at sigwinch dot xyzzy without the zy>
- 0.1 (2020-11)
- Initial release.
- 1.0.0 (2022-08-15)
- Rework exceptions, streamline dependencies, improve tests.
- 1.0.2 (2022-08-16)
- Fix the kind of condition raised by maybe->two-values in some cases.
- 1.0.3 (2022-12-26)
- Fix unresolved identifier problem.
Copyright (C) John Cowan, Wolfgang Corcoran-Mathe (2020).
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.