[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Contracts macro example
From: |
Zelphir Kaltstahl |
Subject: |
Contracts macro example |
Date: |
Thu, 14 Jul 2022 23:55:26 +0000 |
Hello Guile users!
I've tried myself again at writing some macros. Some post about programming
language features inspired me to try and write a macro, which adds contracts (or
my naive idea of what they are) to function definitions.
The code is at:
https://notabug.org/ZelphirKaltstahl/guile-examples/src/d749de48307cebe279215ab5df50853c9d100b2f/macros/contract.scm.
Or here, so that this e-mail can stand on its own:
~~~~
;; Suppose we wanted to check assumptions about arguments to
;; our function. What kind of form could we write to express
;; this?
;; (define-with-contract account-withdraw
;; (requires (< amount account-balance))
;; (ensures (>= account-balance 0))
;; (lambda (amount account-balance)
;; ...))
;; Or abstractly:
;; (define-with-contract func
;; (requires req-pred* ...)
;; (ensures ensure-pred* ...)
;; lambda-expr)
(import
(except (rnrs base) let-values)
(only (guile)
lambda* λ)
(ice-9 exceptions)
(srfi srfi-1))
;; and-raise needs to be a macro, because its arguments must
;; not be immediately evaluated, otherwise we cannot raise
;; an exception containing the failing check.
(define-syntax and-raise
(syntax-rules ()
[(_ (op args* ...) check-expr* ...)
(cond
[(not (op args* ...))
(raise-exception
(make-exception
(make-assertion-failure)
(make-exception-with-message "assertion failed")
(make-exception-with-irritants (quote (op args* ...)))))]
[else
(and-raise check-expr* ...)])]
[(_ #|nothing|#)
#t]))
;; `ensure` builds up an `and` expression, which contains
;; all the conditions.
(define-syntax ensure-with-result
(syntax-rules (ensure)
[(_ identifier expr* ... (op args* ...))
(and-raise
;; insert identifier on the left
(op identifier args* ...)
(ensure-with-result identifier expr* ...))]
;; If there is only one more ensure clause, transform
;; it, and do not place another macro call.
[(_ identifier (op args* ...))
;; insert identifier on the left
(op identifier args* ...)]
;; If there are no more ensure clauses, transform to
;; `#t`, the neutral element of `and`.
[(_ identifier)
#t]))
(define-syntax define-with-contract
(syntax-rules (require ensure <?>)
;; first process ensure (post-conditions)
[(_ function-name
(require reqs* ...)
(ensure ensu-expr* ...)
(lambda (args* ...)
lambda-body-expr* ...))
(define function-name
(lambda (args* ...)
;; temporarily store result of the function
(let ([result
(cond
;; check pre-conditions (requirements)
[(not (and-raise reqs* ...))
(raise-exception
(make-exception
(make-assertion-failure)
(make-exception-with-message "assertion failed")
(make-exception-with-irritants (list args* ...))
(make-exception-with-origin (syntax->datum
function-name))))]
;; otherwise run the body
[else
lambda-body-expr* ...])])
(cond
;; check post-conditions (ensures)
[(not (ensure-with-result result ensu-expr* ...))
;; Problem: Cannot know which post-condition
;; failed. Could be improved.
(raise-exception
(make-exception
(make-assertion-failure)
(make-exception-with-message "assertion failed")
(make-exception-with-irritants (list args* ...))
(make-exception-with-origin (syntax->datum function-name))))]
;; return result if post conditions are true
[else result]))))]))
;; Lets make an example definition: Withdrawing an amount of
;; money from an account, returning the new account balance
;; (although not really mutating the account or anything,
;; really just a toy example).
(define-with-contract account-withdraw
(require (< amount account-balance)
(>= amount 0))
(ensure (>= 0)) ; depends on what the function returns
(lambda (amount account-balance)
(- account-balance amount)))
;; Using the defined function just like any other function.
(display (account-withdraw 10 20)) (newline)
(display (account-withdraw 30 20)) (newline)
~~~~
Are there any, for the more experienced eye, obvious mistakes or bad practices
in there, that should be improved? (especially regarding macros).
Best regards,
Zelphir
--
repositories: https://notabug.org/ZelphirKaltstahl
- Contracts macro example,
Zelphir Kaltstahl <=