Racket’s Missing Predicate Language
1 Syntax
2 Forms
on
switch
lambda/  subject
predicate-lambda
lambdap
π
switch-lambda
λ01
define/  subject
define-predicate
define-switch
8.1

Racket’s Missing Predicate Language

Siddhartha Kasivajhula

 (require ionic) package: ionic

An embedded predicate language to allow convenient framing of programming logic in terms of predicates.

Logic is the soul of language, relations are the basis of logic, and every relation corresponds to a predicate. Predicates in a general sense are everywhere in human languages as well as in programming languages. They are essential in the way that we understand and express ideas, in the way that we think about and write programs. But in the absence of syntax that allows us to express ideas in terms of their subject-predicate structure, such structure must be unraveled and expressed in terms of lower-level syntactic abstractions, abstractions which much be parsed by those reading the code into the higher level subject-predicate structure you had in mind while writing it. This is an unnecessary toll on the conveyance of ideas, one that is eliminated by the present module.

Examples:
> (on (5) (and positive? odd?))

#t

> (on ("5" "5.0") (with-key ->number =))

#t

> (on (5 7) (~> (>< ->string) string-append))

"57"

> (define-predicate (positive-odd? n)
    (and positive? odd?))
> (define-predicate (approximately-equal? m n)
    (~> - abs (< 1)))
> (approximately-equal? 5 7)

#f

> (approximately-equal? 5 5.4)

#t

> (define/subject (root-mean-square vs)
    (~> (map sqr _) (-< sum length) / sqrt))
> (root-mean-square (range 10))

5.338539126015656

> (define-predicate (between-0-and-10? n)
    (<= 0 _ 10))
> (between-0-and-10? 4)

#t

> (between-0-and-10? 12)

#f

> (define-switch (abs n)
    [negative? (call (* -1))]
    [else n])
> (abs -5)

5

> (abs 5)

5

1 Syntax

This section provides a specification of the basic syntax recognizable to all of the predicate forms provided in this module.

2 Forms

The core form that defines and uses the predicate language is on, which can be used to describe arbitrary computations involving predicates, while another form, switch, leverages the former to provide a conditional dispatch form analogous to cond. In addition, other forms like define-predicate and define-switch are provided that leverage these to create functions constrained to the predicate language – for use in defining predicate functions, and dispatch functions, respectively. The advantage of using these forms over the usual general-purpose define form is that constraints provide clarity, minimize redundancy, and provide guardrails against programmer error.

syntax

(on (args) procedure-expr)

(on (args)
  (if [predicate consequent ...]
      ...
      [else consequent ...]))
 
args = arg ...
     
arg = expr
     
predicate = procedure-expr
  | (eq? value-expr)
  | (equal? value-expr)
  | (one-of? value-expr ...)
  | (= value-expr)
  | (< value-expr)
  | (> value-expr)
  | (<= value-expr)
  | ( value-expr)
  | (>= value-expr)
  | ( value-expr)
  | (all predicate)
  | (any predicate)
  | (none predicate)
  | (and predicate ...)
  | (or predicate ...)
  | (not predicate)
  | (and% predicate)
  | (or% predicate)
  | (with-key procedure-expr predicate)
  | (.. predicate ...)
  | (% predicate)
  | (map predicate)
  | (filter predicate)
  | (foldl predicate value-expr)
  | (foldr predicate value-expr)
  | (apply predicate)
     
consequent = expr
  | (call call-expr)
     
call-expr = procedure-expr
  | (.. call-expr ...)
  | (% call-expr)
  | (map call-expr)
  | (filter call-expr)
  | (foldl call-expr value-expr)
  | (foldr call-expr value-expr)
  | (apply call-expr)
     
procedure-expr = any expression evaluating to a procedure
     
value-expr = any expression evaluating to a value
     
expr = any expression
A form for defining predicates. Typically, on should only be used for the general case of evaluating an expression in the context of a pre-defined subject (such as while defining a predicate). For the more specific case of predicate-based dispatch, use switch.

Example:
> (on (5) (and positive? odd?))

#t

syntax

(switch (args ...)
  [predicate consequent ...]
  ...
  [else consequent ...])
A predicate-based dispatch form, usable as an alternative to cond and if.

Example:
> (switch (5)
    [(and positive? odd?) 'yes]
    [else 'no])

'yes

syntax

(lambda/subject args body ...)

syntax

(predicate-lambda args body ...)

syntax

(lambdap args body ...)

syntax

(π args body ...)

Similiar to lambda but constrained to the predicate language. This is exactly equivalent to (lambda args (on (args) body ...)). predicate-lambda, lambdap and π are aliases for lambda/subject.

syntax

(switch-lambda (args ...)
  [predicate consequent ...]
  ...
  [else consequent ...])

syntax

(λ01 (args ...)
  [predicate consequent ...]
  ...
  [else consequent ...])
Similar to lambda but constrained to be a (predicate-based) dispatcher. This is exactly equivalent to (lambda args (switch (args) [predicate consequent ...] ... [else consequent ...])). λ01 is an alias for switch-lambda.

Example:
> ((switch-lambda (x)
     [(and positive? odd?) 'yes]
     [else 'no]) 5)

'yes

syntax

(define/subject (name args) body ...)

syntax

(define-predicate (name args) body ...)

Similiar to the function form of define but constrained to the predicate language. This is exactly equivalent to (define name (lambda/subject args body ...)). define-predicate is an alias for define/subject.

syntax

(define-switch (args ...)
  [predicate consequent ...]
  ...
  [else consequent ...])
Similiar to the function form of define but constrained to be a (predicate-based) dispatcher. This is exactly equivalent to (define name (switch-lambda args [predicate consequent ...] ... [else consequent ...])).