Re: define-typed: checking values on proc entry and exit

"Dr. Arne Babenhauserheide" <[email protected]>
Newsgroups gmane.lisp.guile.devel,gmane.lisp.guile.user
Message-ID <[email protected]>
"Dr. Arne Babenhauserheide" <[email protected]> writes:

> Zelphir Kaltstahl <[email protected]> writes:
>> https://codeberg.org/ZelphirKaltstahl/guile-examples/src/commit/0e231c289596cb4c445efb30168105914a8539a5/macros/contracts

> And the *-versions are ominous: optional and keyword arguments may be
> the next frontier.
>
> I’m not sure how to keep those simple.

I now have a solution: https://www.draketo.de/software/guile-snippets#define-typed

┌────
│ (import (srfi :11 let-values))
│ (define-syntax-rule (define-typed (procname args ...) (ret? types ...) body ...)
│   (begin
│     (define* (procname args ...)
│       ;; create a sub-procedure to run after typecheck
│       (define (helper)
│         body ...)
│       ;; use a typecheck prefix for the arguments   
│       (map (λ (type? argument)
│              (let ((is-keyword? (and (keyword? type?)
│                                      (keyword? argument))))
│                (when (and is-keyword? (not (equal? type? argument)))
│                  (error "Keywords in arguments and types are not equal ~a ~a"
│                    type? argument))
│                (unless (or is-keyword? (type? argument))
│                  (error "type error ~a ~a" type? argument))))
│            (list types ...) (list args ...))
│       ;; get the result
│       (let-values ((res (helper)))
│         ;; typecheck the result
│         (unless (apply ret? res)
│           (error "type error: return value ~a does not match ~a"
│                  res ret?))
│         ;; return the result
│         (apply values res)))
│     (unless (equal? (length (quote (args ...))) (length (quote (types ...))))
│       (error "argument error: argument list ~a and type list ~a have different size"
│         (quote (args ...)) (quote (types ...))))
│     ;; add procedure properties via an inner procedure
│     (let ((helper (lambda* (args ...) body ...)))
│       (set-procedure-properties! procname (procedure-properties helper))
│       ;; preserve the name
│       (set-procedure-property! procname 'name 'procname))))
└────

This supports most features of regular define like docstrings, procedure
properties, multiple values (thanks to Vivien!), keyword-arguments
(thanks to Zelphir Kaltstahl’s [contracts]), and so forth.

Basic usage:

┌────
│ (define-typed (hello typed-world) (string? string?)
│   typed-world)
│ (hello "typed")
│ ;; => "typed"
│ (hello 1337)
│ ;; => type error ~a ~a #<procedure string? (_)> 1337
│ (define-typed (hello typed-world) (string? string?)
│   "typed" ;; docstring
│   #((props)) ;; more properties
│   1337) ;; wrong return type
│ (procedure-documentation hello)
│ ;; => "typed"
│ (procedure-properties hello)
│ ;; => ((name . hello) (documentation . "typed") (props))
│ (hello "typed")
│ ;; type error: return value ~a does not match ~a (1337) #<procedure string? (_)>
└────

Multiple Values and optional and required keyword arguments:

┌────
│ (define-typed (multiple-values num) ((λ(a b) (> a b)) number?)
│   (values (* 2 (abs num)) num))
│ (multiple-values -3)
│ ;; => 6
│ ;; => -3
│ (define-typed (hello #:key typed-world) (string? #:key string?) "typed" #((props)) typed-world)
│ (hello #:typed-world "foo")
│ ;; => "foo"
│ ;; unused keyword arguments are always boolean #f as input
│ (hello)
│ ;; => type error ~a ~a #<procedure string? (_)> #f
│ ;; typing optional keyword arguments
│ (define (optional-string? x) (or (not x) (string? x)))
│ (define-typed (hello #:key typed-world) (string? #:key optional-string?)
│   (or typed-world "world"))
│ (hello)
│ ;; => "world"
│ (hello #:typed-world "typed")
│ ;; => "typed"
│ (hello #:typed-world #t)
│ ;; => type error ~a ~a #<procedure optional-string? (x)> #t
│ ;; optional arguments
│ (define-typed (hello #:optional typed-world) (string? #:optional optional-string?)
│   (or typed-world "world"))
│ (hello)
│ ;; => "world"
│ (hello "typed")
│ ;; => "typed"
│ (hello #t)
│ ;; => type error ~a ~a #<procedure optional-string? (x)> #t
└────

Best wishes,
Arne
signature.asc (application/pgp-signature, 1.1 KB)
-----BEGIN PGP SIGNATURE-----

iQJEBAEBCAAuFiEE801qEjXQSQPNItXAE++NRSQDw+sFAmZD/WMQHGFybmVfYmFi
QHdlYi5kZQAKCRAT741FJAPD6084D/9KQEBK3XxggLF87WNGUXzN8lW42+0W+z4/
BqZkdRwxSAiVpdvOJKjO8uxlDu6hHPSz4V0lsbqKgjGCewjPWdIK7mGWne3G49DI
Mpin7E6fHz/MzMsPmOxp1F8jVtOrfkVyJrKeMMSm51oDqO5jLlGLRm8IljrmgD0T
gNDv+OBOV1iczYqCRk2VL2qgrhqIkm6tsm0VApO7Yo1xw4YEdglx/d5baOk09txp
r6YOymxjDAgE4jlqhEMIVsDEh62OHJasNEWA0Tk8Zgbreuk9u3+EL6AHWSJQIWeB
XiLiYavJdo/2DCQAFf2MOp5LxV6+qUFsjfvbjKQafZaPqpm8RtaYHim3lcIqxgiK
IlYHXas1O0Eh0y/tedWKpWoXtBj44uGQ0NugmR/sHw28V/Doil8vNEHPSELf5jL1
LXqbZ+jxTlwl+vPU/w2bUDJ6oZ8u9DOC8v7Hg/I0Gvfw8WqcGZzxmYs6JOjRvP8V
7ySGkIeBMG7R02BdaliG09Rmq/7CXxxroECxXcQVm1iiRmyAA1+11nGCxg6LPOh9
8saf/946lcOnQuhdwsC+63IIGU2qZp12ymSQsJvTfp2KxVUYHfw5JvgZGt/bVsQm
Lfk8bZvvR3xgmCfcpCYtcjWU4gVe8u+oVkg+gOcwbZPsoM1h1xhi6zngG1L3txu5
6j0y1WJJ2ojEBAEBCAAuFiEE3Si95tmHXKvOSosd3M8NswvBBUgFAmZD/WMQHGFy
bmVfYmFiQHdlYi5kZQAKCRDczw2zC8EFSOfpA/9SSZ9FfNC8d45Vj6h8U86SA0oO
BNmETOnEo4HaP0ELNb3uaz29HmgGewiv7mxky0180tao6JV1G/aTlRHVnzMYByAR
En1aZB63a/km6gKKizzq/oTdrDE1xPxFOE79SEwnhiomAmTTIROy2Tsum4sWRjes
P0optjNwD/4lCChzMA==
=GvR0
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.