by José Bollo
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-266@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
The syntax expr allows one to
write arithmetic expressions using a syntax near to mathematical
notation, potentially improving the readability of Scheme programs.
Scheme programmers expressing arithmetic expression see all over their code the gap between what they learnt at school for expressing arithmetic formulae and what is written in Scheme.
Here is an example of Scheme for computing the root of a quadratic and the Newton transform.
Here is the common Scheme code:
(let ((delta (sqrt (- (* b b) (* 4 a c)))))
(values (/ (+ (- b) delta) (* 2 a))
(/ (- (- b) delta) (* 2 a))))
(define (deriv g)
(lambda (x) (/ (- (g (+ x dx)) (g x)) dx)))
(define (newton-transform g)
(lambda (x) (- x (/ (g x) ((deriv g) x)))))
This SRFI describes the syntax expr that
fills the gap between Scheme and mathematics, at least for arithmetic.
Using this syntax, the above examples can be written as below:
(expr sqrt(b * b - 4 * a * c) as delta in
values ((- b + delta) / (2 * a)
(- b - delta) / (2 * a)))
(define (deriv g)
(lambda (x) (expr (g(x + dx) - g(x)) / dx)))
(define (newton-transform g)
(lambda (x) (expr x - g(x) / deriv(g)(x))))
The syntax expr is inspired by the eponymous
UNIX tool expr. The expression at its right is expressed using almost
standard formulae.
Writing arithmetic expressions is common when programming.
Writing logic expressions and comparison expressions is also
quite common. For this reason, the syntax expr
also handles comparisons and logic operators.
Thus instead of writing:
(if (and (< a 1) (not (= (+ b c) 9)))
...
It is possible to write:
(if (expr a < 1 and b + c != 9))
...
A previous SRFI, the SRFI 105, Curly-infix-expressions, already introduced the ability to write expressions in a more usual way. The current SRFI differs of SRFI 105 on the following points:
expr.However, SRFI-105 is compatible with this SRFI when $nfx$ and is defined as below:
(define-syntax $nfx$
(syntax-rules ()
((_ x ...) (expr x ...))))
According to SRFI 261, items exported by this SRFI can be imported using:
(import (srfi srfi-266))
or on system supporting it, using:
(import (srfi expr-266))
This SRFI exports one main syntax item: expr.
It also exports expr-set-prefix,
expr-set-left-infix,
expr-set-right-infix,
expr-set-list,
expr-set-compare,
expr-set-ternary
that serve to define or redefine operators (see below).
The syntactic item expr should be followed by
an expression E ...:
(expr E ...)
The syntax expr transforms the
expression E ... to its equivalent scheme expression during
syntaxic expansion.
It means that using expr has no runtime cost.
For example:
(expr a + b + c < d <= x - y - z)
Should be translated something looking like:
(let ((temporary-1 d))
(and (< (+ a b c) temporary-1)
(<= temporary-1 (- x y z))))
The expression E ... follows the grammar of EXPRESSION defined below:
EXPRESSION := FORMULA
| CALL
FORMULA := TERM
| FORMULA infix-operator TERM
TERM := VALUE
| prefix-operator TERM
VALUE := constant
| symbol
| '(' EXPRESSION ')'
CALL := FORMULA '(' ARGUMENTS ')'
| FORMULA EXPRESSION
ARGUMENTS := FORMULA
| ARGUMENTS FORMULA
The expression E ... is made of constants, variables,
operators, calls and sub-expressions.
Constants are number, strings, vectors, booleans, ...
Variables are symbols related to variables bound in the context of the expression.
Operators are keywords. They are scanned as symbols, not as bindings to values. This rule is required for providing an unambiguous translation of a rich set of operators, and to provision it without requiring any import of any library.
CAUTION: because of this rule, a potential clash may occur when the same symbol is used for an operator and a variable.
The SRFI comes with a predefined set of operators. These operators are listed below.
Operators are of one of the below types:
+).
<).
if/else).
Types of operators are described below.
Operators have a priority number. The lower the priority number is, the most prioritized is the operator.
Example of priority of operators: the predefined operators
+ and * have respective priority numbers of
50 and 40. Thus:
(expr a + b * c)
becomes
(+ a (* b c))
(expr a * b + c)
becomes
(+ (* a b) c)
Calls are right associatives.
Calls are made of two parts: the called and the arguments:
Examples:
(expr a b)
becomes
(a b)
(expr a b c)
becomes
(a (b c))
(right associativity)
(expr a(b c))
becomes
(a b c)
(expr a(b) + c)
becomes
(+ (a b) c)
(expr a((b c) d))
becomes
(a (b c) d)
(expr a(b - c))
becomes
(a (- b c))
(expr a(b (- c)))
becomes
(a (b (- c))
Sub-expressions are expressions enclosed in parentheses.
Sub expressions are of 3 different types depending one the context.
Sub expression are detailed below.
The syntax expr is not required to
optimize expressions. It is only required to translate the expression
(for example, (expr + - 3) should become (+ (- 3))
even if it looks like being -3).
Types of operators that can be defined are: prefix, left, right, list, comp, ternary.
Here is how it behaves when used:
Prefix operator for unary operation.
OPERATOR X becomes
(OPERATION X).
Infix operator for binary operation.
X OPERATOR Y becomes
(OPERATION X Y).
Left associative.
X OPERATOR Y OPERATOR Z becomes
(OPERATION (OPERATION X Y) Z).
Infix operator for binary operation.
X OPERATOR Y becomes
(OPERATION X Y).
Right associative.
X OPERATOR Y OPERATOR Z becomes
(OPERATION X (OPERATION Y Z)).
Infix operator for list operation.
X OPERATOR Y [OPERATOR Z]... becomes
(OPERATION X Y Z...).
Infix operator for compare operation.
X OPERATOR Y [OPERATOR Z]... becomes
(OPERATION X Y Z...).
Heterogeneous comparison.
X COMP1 [Y COMP1]... Z COMP2 A [COMP2 B]... becomes
(let ((tmp Z)) (and (COMP1 X Y... tmp) (COMP2 tmp A B...))).
Pair of operators for ternary operation.
X FIRST-OPERATOR Y SECOND-OPERATOR Z becomes
(OPERATION X Y Z).
Case of predefined ... if ... else ....
X if Y else Z becomes
(if Y X Z).
These type above can be used for defining new operators.
See below for details on defining or
redefining operators for syntax expr.
The below operators are of a type that can not be defined because they have a specialized implementation::
No operator for function call operation.
X Y becomes
((expr X) (expr-for-args Y)).
Space separated arguments.
X (ARG1 ARG2 ...) becomes
((expr X) (expr ARG1) (expr ARG2) ...).
Right associative.
X Y Z becomes
(X (Y Z)).
Special assignment within expression.
X as Y ... in Z becomes
(let-values (((Y ...) X)) Z).
Expressions can have sub-expressions in parentheses.
It occurs in three cases.
When argument of a call, spaces are interpreted as separation of arguments.
Then f (a b ...) expands as
((expr f) (expr a) (expr b) ...).
Except if at the place of an argument of a call,
and except if made of only one sub-expression,
a sub-expression (a ...)
expands as (expr a ...).
Except if at the place of an argument of a call,
when a sub-expression only contains one sub-expression,
it expands to the inner sub-express, literally,
literally by this subexpression. Then ((a ...))
becomes (a ...) without applying implicitly expr
to the enclosed subexpression.
It is possible to define or to redefine operators. Definition occurs when the operator is not already defined. Redefinition occurs when the operator is already defined. In that case, the new definition replaces the older one.
Definition or redefinition set the priority of the operator as explained here.
Definition or redefinition are done using syntaxic helpers. These helpers are of two main types: basic or procedural.
Basic helpers are:
(expr-set-prefix oper priority procname)
Tells to replace expressions ... oper a ...
by (procname a).
(expr-set-left-infix oper priority procname)
Tells to replace expressions ... a oper b oper c ...
by (procname (procname a b) c).
Applies left associativity.
(expr-set-right-infix oper priority procname)
Tells to replace expressions ... a oper b oper c ...
by (procname a (procname b c)).
Applies right associativity.
(expr-set-list oper priority procname)
Tells to replace expressions ... a oper b oper c ...
by (procname a b c).
(expr-set-compare oper priority procname)
Tells to replace expressions ... a oper b oper c ...
by (procname a b c).
(expr-set-ternary oper1 oper2 priority procname)
Tells to replace expressions ... a oper1 b oper2 c ...
by (procname a b c).
Procedural helpers are looking like the below definition (except that lambdas are not duplicated):
(expr-set-prefix oper priority (x) expr)
Tells to replace expressions ... oper a ...
by ((lambda(x) expr) a).
(expr-set-left-infix oper priority (x y) expr)
Tells to replace expressions ... a oper b oper c ...
by ((lambda(x y) expr) ((lambda(x y) expr) a b) c).
Applies left associativity.
(expr-set-right-infix oper priority (x y) expr)
Tells to replace expressions ... a oper b oper c ...
by ((lambda(x y) expr) a ((lambda(x y) expr) b c)).
Applies right associativity.
(expr-set-list oper priority x expr)
Tells to replace expressions ... a oper b oper c ...
by ((lambda x expr) a b c).
(expr-set-compare oper priority x expr)
Tells to replace expressions ... a oper b oper c ...
by ((lambda x expr) a b c).
(expr-set-ternary oper1 oper2 priority (x y z) expr)
Tells to replace expressions ... a oper1 b oper2 c ...
by ((lambda(x y z) expr) a b c).
Here is the list of predefined operators for
expr:
| operator | type | priority | operation |
|---|---|---|---|
| @ | left | 10 | vector-ref |
| @. | left | 10 | list-ref |
| @@ | left | 10 | bytevector-u8-ref |
| @ | prefix | 10 | unbox |
| call | 10 | function call | |
| ** | left | 20 | expt |
| - | prefix | 30 | - |
| + | prefix | 30 | + |
| not | prefix | 30 | not |
| ? | prefix | 30 | boolean --> 0 or 1 |
| * | list | 40 | * |
| / | list | 40 | / |
| \ | left | 40 | quotient |
| % | left | 40 | remainder |
| + | list | 50 | + |
| - | list | 50 | - |
| < | comp | 80 | < |
| > | comp | 80 | > |
| <= | comp | 80 | <= |
| >= | comp | 80 | >= |
| = | comp | 80 | = |
| != | left | 90 | not = |
| and | list | 130 | and |
| or | list | 140 | or |
| implies | left | 150 | implies |
| if else | ternary | 160 | if |
| as in | as | 160 | let-values |
| ~ | prefix | 30 | bitwise-not |
| << | left | 60 | bitwise-arithmetic-shift-left |
| >> | left | 60 | bitwise-arithmetic-shift-right |
| & | list | 100 | bitwise-and |
| ^ | list | 110 | bitwise-xor |
| : | list | 120 | bitwise-ior |
| ~& | left | 100 | bitwise-nand |
| ~^ | left | 110 | bitwise-eqv |
| ~: | left | 120 | bitwise-nor |
| fx- | prefix | 30 | fxneg |
| fx~ | prefix | 30 | fxnot |
| fx* | left | 40 | fx* |
| fx\ | left | 40 | fxquotient |
| fx% | left | 40 | fxremainder |
| fx+ | left | 50 | fx+ |
| fx- | left | 50 | fx- |
| fx<< | left | 60 | fxarithmetic-shift-left |
| fx>> | left | 60 | fxarithmetic-shift-right |
| fx< | comp | 80 | fx<? |
| fx> | comp | 80 | fx>? |
| fx<= | comp | 80 | fx<=? |
| fx>= | comp | 80 | fx>=? |
| fx= | comp | 80 | fx=? |
| fx!= | left | 90 | not fx=? |
| fx& | list | 100 | fxand |
| fx^ | list | 110 | fxxor |
| fx: | list | 120 | fxior |
| fl- | prefix | 30 | fl- |
| fl* | left | 40 | fl* |
| fl/ | left | 40 | fl/ |
| fl\ | left | 40 | flquotient |
| fl% | left | 40 | flremainder |
| fl+ | left | 50 | fl+ |
| fl- | left | 50 | fl- |
| fl< | comp | 80 | fl<? |
| fl> | comp | 80 | fl>? |
| fl<= | comp | 80 | fl<=? |
| fl>= | comp | 80 | fl>=? |
| fl= | comp | 80 | fl=? |
| fl!= | left | 90 | not fl=? |
Implementations of expr can be made
using standard Scheme macros, using implementation specific macros, or,
using internals of implementations.
The sample implementation uses syntax-case. It works for Guile 3 and ChezScheme.
Source for the sample implementation.Many thanks to writers of the UNIX tool expr.
© 2026 José Bollo.
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.