From: Zelphir Kaltstahl <zelphirkaltstahl@posteo.de>
To: Guile User <guile-user@gnu.org>
Subject: Contracts macro example
Date: Thu, 14 Jul 2022 23:55:26 +0000 [thread overview]
Message-ID: <a2ac0002-b3a1-7edf-87b1-1cfddc630513@posteo.de> (raw)
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
next reply other threads:[~2022-07-14 23:55 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-07-14 23:55 Zelphir Kaltstahl [this message]
2022-07-19 15:20 ` Contracts macro example Maxime Devos
2022-07-20 8:39 ` Zelphir Kaltstahl
2022-07-20 8:55 ` Maxime Devos
2022-07-24 1:21 ` Zelphir Kaltstahl
2022-08-04 16:19 ` Maxime Devos
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/guile/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=a2ac0002-b3a1-7edf-87b1-1cfddc630513@posteo.de \
--to=zelphirkaltstahl@posteo.de \
--cc=guile-user@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).