|(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.
> (on (5) (and positive? odd?))
> (on ("5" "5.0") (with-key ->number =))
> (on (5 7) ( (>< ->string) string-append))
> (define-predicate (positive-odd? n) (and positive? odd?))
> (define-predicate (approximately-equal? m n) ( - abs (< 1))) > (approximately-equal? 5 7)
> (approximately-equal? 5 5.4)
> (define/subject (root-mean-square vs) ( (map sqr _) (-< sum length) / sqrt)) > (root-mean-square (range 10))
> (define-predicate (between-0-and-10? n) (<= 0 _ 10)) > (between-0-and-10? 4)
> (between-0-and-10? 12)
> (define-switch (abs n) [negative? (call (* -1))] [else n]) > (abs -5)
> (abs 5)
This section provides a specification of the basic syntax recognizable to all of the predicate forms provided in this module.
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.
(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
(switch (args ...) [predicate consequent ...] ... [else consequent ...])
(lambda/subject args body ...)
(predicate-lambda args body ...)
(lambdap args body ...)
(π args body ...)
(switch-lambda (args ...) [predicate consequent ...] ... [else consequent ...])
(λ01 (args ...) [predicate consequent ...] ... [else consequent ...])
(define-switch (args ...) [predicate consequent ...] ... [else consequent ...])