From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Linas Vepstas Newsgroups: gmane.lisp.guile.devel,gmane.lisp.guile.user Subject: Re: define-typed: checking values on proc entry and exit Date: Mon, 20 May 2024 21:30:28 -0500 Message-ID: References: <871q6axg4s.fsf@web.de> <97402bb8-b4e9-44a1-9955-23e95eda6f5d@posteo.de> <87v83huq87.fsf@web.de> <87r0e4szod.fsf@web.de> Reply-To: linasvepstas@gmail.com Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="0000000000009639030618ed9a0e" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="2651"; mail-complaints-to="usenet@ciao.gmane.io" Cc: guile-user@gnu.org, Zelphir Kaltstahl , guile-devel@gnu.org To: "Dr. Arne Babenhauserheide" Original-X-From: guile-devel-bounces+guile-devel=m.gmane-mx.org@gnu.org Tue May 21 04:31:14 2024 Return-path: Envelope-to: guile-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1s9FHW-0000VX-05 for guile-devel@m.gmane-mx.org; Tue, 21 May 2024 04:31:14 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s9FH7-0007Gz-Pe; Mon, 20 May 2024 22:30:49 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s9FH6-0007GY-Ep; Mon, 20 May 2024 22:30:48 -0400 Original-Received: from mail-oa1-x2c.google.com ([2001:4860:4864:20::2c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1s9FH0-0007AP-NN; Mon, 20 May 2024 22:30:47 -0400 Original-Received: by mail-oa1-x2c.google.com with SMTP id 586e51a60fabf-23dd4dca5dbso1085626fac.0; Mon, 20 May 2024 19:30:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1716258640; x=1716863440; darn=gnu.org; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=ZKpbsYGz2CLCTsrTN3JsQHr9LU+ATF0jdv81m6v+sZo=; b=g3jdctTZkXDGLQT7EfGBhyU8AnATKqwmEe/3F2pEZ1q/EteWgEXvXp15ih7wwqbLcJ y2vbWsMl8LcQXboShbUiKjSsejBpzUacCT6J/eNxX3ascSDnuIOBdQ0QPjVGKn+tzVpk ofbsEmQ52MqiHZnMWzIf1891I7B7JYqsYkjkQzrKdHXf3zsa96FlMvYRqqLQKpTBBeA4 ZIcHQLEsQpTvJhFbptnmFPvLYMgFELcDju7FleMQNI6gO/BLOjjdaSpkEgoxxbdIGUme OkCqacH9c0x124Al7g496SwBxcMZ0hp3RsV/KtSmwt0ao8tS0JNrdKe/9eEsJ0g6tE1e 8A1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1716258640; x=1716863440; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=ZKpbsYGz2CLCTsrTN3JsQHr9LU+ATF0jdv81m6v+sZo=; b=CcyIqB0/wy6GXF53NegSdlnorBiljiij+OTx9RaOHhnvoHRkJRhBBeV2FZzmtnPbVo IbEx/e8u9DP+zT/fh/YGn5/aXQz0nUqa7faML2AR0TSBuyiLKkG6p9VyNXZ00oqrpYx7 YosNpi9SeBIRS4yj0YdgabPEK4po5enXgx+ejiVBbIQOJ36Pm5n5MWRq2TLb+H+NBu+P 7062IciTuwe0JNvBjviXxMogjo9EB6aTnjMexYoztt7fZ+dZzqCBqOtsf2m5IEdTY0pH YtEKbBge4FYEHXOQU814iKwxJKwbTLXQat3CzXtIHT46qGuBUdHa5krK+D+YJ9KQXdMr k0xg== X-Forwarded-Encrypted: i=1; AJvYcCVREr6MpjxqiOeoymqspJAhqunWigNHYqXLE51ArWyfASBTCYBAyRmDDKYmqdw3s/RHIHy/AwYxONGSbarjeYjcsKYx X-Gm-Message-State: AOJu0Yx312/oEY7Gc6hi1WccVmY+0bW5Rlwm9a48wzn3u6b6zZdMjFQx gVCr/bmzn3pvEJzZK4GCeM+F4ADc5lz+xhOjb8G7zP2bJT7hGQCUx+AYH1UhYL/UMxWQg46fyLG 1rsPd/HB58KvQtwJDWi8vx2bLJvL7xg== X-Google-Smtp-Source: AGHT+IEqf96n/bjKd3Z8glGomLLunZB6EgE7JRCizrORdsIU0UdNLEk9pxCad0cc/JbI+Ix0/WO82hVleR7AkFdODn4= X-Received: by 2002:a05:6870:f149:b0:24c:4ca2:e009 with SMTP id 586e51a60fabf-24c4ca2e63dmr383427fac.18.1716258640114; Mon, 20 May 2024 19:30:40 -0700 (PDT) In-Reply-To: <87r0e4szod.fsf@web.de> Received-SPF: pass client-ip=2001:4860:4864:20::2c; envelope-from=linasvepstas@gmail.com; helo=mail-oa1-x2c.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, T_SPF_TEMPERROR=0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: guile-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Developers list for Guile, the GNU extensibility library" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guile-devel-bounces+guile-devel=m.gmane-mx.org@gnu.org Original-Sender: guile-devel-bounces+guile-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.lisp.guile.devel:22420 gmane.lisp.guile.user:19664 Archived-At: --0000000000009639030618ed9a0e Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable One of my old interests has been storing data as s-expressions. But just "storing" data is kind of useless, unless you can also search through it, query it. And, from experience, search/query is mostly useless without typing. Is `(foo (bar baz))` of type `(surname (given-name occupation))` or is it `(state (county station))` ? How might one know? I'm not asking for help here (I have de facto answers to these questions) I'm just .. offering something to think about. -- Linas On Tue, May 14, 2024 at 7:10=E2=80=AFPM Dr. Arne Babenhauserheide wrote: > "Dr. Arne Babenhauserheide" writes: > > > Zelphir Kaltstahl writes: > >> > https://codeberg.org/ZelphirKaltstahl/guile-examples/src/commit/0e231c289= 596cb4c445efb30168105914a8539a5/macros/contracts > > > And the *-versions are ominous: optional and keyword arguments may be > > the next frontier. > > > > I=E2=80=99m not sure how to keep those simple. > > I now have a solution: > https://www.draketo.de/software/guile-snippets#define-typed > > =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80 > =E2=94=82 (import (srfi :11 let-values)) > =E2=94=82 (define-syntax-rule (define-typed (procname args ...) (ret? typ= es ...) > body ...) > =E2=94=82 (begin > =E2=94=82 (define* (procname args ...) > =E2=94=82 ;; create a sub-procedure to run after typecheck > =E2=94=82 (define (helper) > =E2=94=82 body ...) > =E2=94=82 ;; use a typecheck prefix for the arguments > =E2=94=82 (map (=CE=BB (type? argument) > =E2=94=82 (let ((is-keyword? (and (keyword? type?) > =E2=94=82 (keyword? argument)))) > =E2=94=82 (when (and is-keyword? (not (equal? type? argume= nt))) > =E2=94=82 (error "Keywords in arguments and types are no= t equal > ~a ~a" > =E2=94=82 type? argument)) > =E2=94=82 (unless (or is-keyword? (type? argument)) > =E2=94=82 (error "type error ~a ~a" type? argument)))) > =E2=94=82 (list types ...) (list args ...)) > =E2=94=82 ;; get the result > =E2=94=82 (let-values ((res (helper))) > =E2=94=82 ;; typecheck the result > =E2=94=82 (unless (apply ret? res) > =E2=94=82 (error "type error: return value ~a does not match ~a= " > =E2=94=82 res ret?)) > =E2=94=82 ;; return the result > =E2=94=82 (apply values res))) > =E2=94=82 (unless (equal? (length (quote (args ...))) (length (quote = (types > ...)))) > =E2=94=82 (error "argument error: argument list ~a and type list ~a= have > different size" > =E2=94=82 (quote (args ...)) (quote (types ...)))) > =E2=94=82 ;; add procedure properties via an inner procedure > =E2=94=82 (let ((helper (lambda* (args ...) body ...))) > =E2=94=82 (set-procedure-properties! procname (procedure-properties= helper)) > =E2=94=82 ;; preserve the name > =E2=94=82 (set-procedure-property! procname 'name 'procname)))) > =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80 > > This supports most features of regular define like docstrings, procedure > properties, multiple values (thanks to Vivien!), keyword-arguments > (thanks to Zelphir Kaltstahl=E2=80=99s [contracts]), and so forth. > > Basic usage: > > =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80 > =E2=94=82 (define-typed (hello typed-world) (string? string?) > =E2=94=82 typed-world) > =E2=94=82 (hello "typed") > =E2=94=82 ;; =3D> "typed" > =E2=94=82 (hello 1337) > =E2=94=82 ;; =3D> type error ~a ~a # 1337 > =E2=94=82 (define-typed (hello typed-world) (string? string?) > =E2=94=82 "typed" ;; docstring > =E2=94=82 #((props)) ;; more properties > =E2=94=82 1337) ;; wrong return type > =E2=94=82 (procedure-documentation hello) > =E2=94=82 ;; =3D> "typed" > =E2=94=82 (procedure-properties hello) > =E2=94=82 ;; =3D> ((name . hello) (documentation . "typed") (props)) > =E2=94=82 (hello "typed") > =E2=94=82 ;; type error: return value ~a does not match ~a (1337) # string? (_)> > =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80 > > Multiple Values and optional and required keyword arguments: > > =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80 > =E2=94=82 (define-typed (multiple-values num) ((=CE=BB(a b) (> a b)) numb= er?) > =E2=94=82 (values (* 2 (abs num)) num)) > =E2=94=82 (multiple-values -3) > =E2=94=82 ;; =3D> 6 > =E2=94=82 ;; =3D> -3 > =E2=94=82 (define-typed (hello #:key typed-world) (string? #:key string?)= "typed" > #((props)) typed-world) > =E2=94=82 (hello #:typed-world "foo") > =E2=94=82 ;; =3D> "foo" > =E2=94=82 ;; unused keyword arguments are always boolean #f as input > =E2=94=82 (hello) > =E2=94=82 ;; =3D> type error ~a ~a # #f > =E2=94=82 ;; typing optional keyword arguments > =E2=94=82 (define (optional-string? x) (or (not x) (string? x))) > =E2=94=82 (define-typed (hello #:key typed-world) (string? #:key optional= -string?) > =E2=94=82 (or typed-world "world")) > =E2=94=82 (hello) > =E2=94=82 ;; =3D> "world" > =E2=94=82 (hello #:typed-world "typed") > =E2=94=82 ;; =3D> "typed" > =E2=94=82 (hello #:typed-world #t) > =E2=94=82 ;; =3D> type error ~a ~a # #t > =E2=94=82 ;; optional arguments > =E2=94=82 (define-typed (hello #:optional typed-world) (string? #:optiona= l > optional-string?) > =E2=94=82 (or typed-world "world")) > =E2=94=82 (hello) > =E2=94=82 ;; =3D> "world" > =E2=94=82 (hello "typed") > =E2=94=82 ;; =3D> "typed" > =E2=94=82 (hello #t) > =E2=94=82 ;; =3D> type error ~a ~a # #t > =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80 > > Best wishes, > Arne > --=20 Patrick: Are they laughing at us? Sponge Bob: No, Patrick, they are laughing next to us. --0000000000009639030618ed9a0e Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
One of my old interests has been storing data as s-ex= pressions. But just "storing" data is kind of useless, unless you= can also search through it, query it. And, from experience, search/query i= s mostly useless without typing. Is `(foo (bar baz))` of type `(surname (gi= ven-name occupation))`=C2=A0 or is it `(state (county station))` ? How migh= t one know? I'm not asking for help here (I have de facto answers to th= ese questions) I'm just .. offering something to think about.

-- Linas

On Tue, May 14, 2024 at 7:10=E2=80=AFPM D= r. Arne Babenhauserheide <arne_bab@we= b.de> wrote:
"Dr. Arne Babenhauserheide" <arne_bab@web.de> writes:

> Zelphir Kaltstahl <zelphirkaltstahl@posteo.de> writes:
>> https://codeberg.org/ZelphirKaltstahl/guile-e= xamples/src/commit/0e231c289596cb4c445efb30168105914a8539a5/macros/contract= s

> And the *-versions are ominous: optional and keyword arguments may be<= br> > the next frontier.
>
> I=E2=80=99m not sure how to keep those simple.

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

=E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80
=E2=94=82 (import (srfi :11 let-values))
=E2=94=82 (define-syntax-rule (define-typed (procname args ...) (ret? types= ...) body ...)
=E2=94=82=C2=A0 =C2=A0(begin
=E2=94=82=C2=A0 =C2=A0 =C2=A0(define* (procname args ...)
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0;; create a sub-procedure to run after = typecheck
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0(define (helper)
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0body ...)
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0;; use a typecheck prefix for the argum= ents=C2=A0 =C2=A0
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0(map (=CE=BB (type? argument)
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (let ((is-keyword= ? (and (keyword? type?)
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (keyword= ? argument))))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (when (and= is-keyword? (not (equal? type? argument)))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (er= ror "Keywords in arguments and types are not equal ~a ~a"
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 type? argument))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (unless (o= r is-keyword? (type? argument))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (er= ror "type error ~a ~a" type? argument))))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (list types ...) (list a= rgs ...))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0;; get the result
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0(let-values ((res (helper)))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0;; typecheck the result
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(unless (apply ret? res)
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(error "type error: = return value ~a does not match ~a"
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 res= ret?))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0;; return the result
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(apply values res)))
=E2=94=82=C2=A0 =C2=A0 =C2=A0(unless (equal? (length (quote (args ...))) (l= ength (quote (types ...))))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0(error "argument error: argument l= ist ~a and type list ~a have different size"
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(quote (args ...)) (quote (types= ...))))
=E2=94=82=C2=A0 =C2=A0 =C2=A0;; add procedure properties via an inner proce= dure
=E2=94=82=C2=A0 =C2=A0 =C2=A0(let ((helper (lambda* (args ...) body ...)))<= br> =E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0(set-procedure-properties! procname (pr= ocedure-properties helper))
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0;; preserve the name
=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0(set-procedure-property! procname '= name 'procname))))
=E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80

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

Basic usage:

=E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80
=E2=94=82 (define-typed (hello typed-world) (string? string?)
=E2=94=82=C2=A0 =C2=A0typed-world)
=E2=94=82 (hello "typed")
=E2=94=82 ;; =3D> "typed"
=E2=94=82 (hello 1337)
=E2=94=82 ;; =3D> type error ~a ~a #<procedure string? (_)> 1337 =E2=94=82 (define-typed (hello typed-world) (string? string?)
=E2=94=82=C2=A0 =C2=A0"typed" ;; docstring
=E2=94=82=C2=A0 =C2=A0#((props)) ;; more properties
=E2=94=82=C2=A0 =C2=A01337) ;; wrong return type
=E2=94=82 (procedure-documentation hello)
=E2=94=82 ;; =3D> "typed"
=E2=94=82 (procedure-properties hello)
=E2=94=82 ;; =3D> ((name . hello) (documentation . "typed") (p= rops))
=E2=94=82 (hello "typed")
=E2=94=82 ;; type error: return value ~a does not match ~a (1337) #<proc= edure string? (_)>
=E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80

Multiple Values and optional and required keyword arguments:

=E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80
=E2=94=82 (define-typed (multiple-values num) ((=CE=BB(a b) (> a b)) num= ber?)
=E2=94=82=C2=A0 =C2=A0(values (* 2 (abs num)) num))
=E2=94=82 (multiple-values -3)
=E2=94=82 ;; =3D> 6
=E2=94=82 ;; =3D> -3
=E2=94=82 (define-typed (hello #:key typed-world) (string? #:key string?) &= quot;typed" #((props)) typed-world)
=E2=94=82 (hello #:typed-world "foo")
=E2=94=82 ;; =3D> "foo"
=E2=94=82 ;; unused keyword arguments are always boolean #f as input
=E2=94=82 (hello)
=E2=94=82 ;; =3D> type error ~a ~a #<procedure string? (_)> #f
=E2=94=82 ;; typing optional keyword arguments
=E2=94=82 (define (optional-string? x) (or (not x) (string? x)))
=E2=94=82 (define-typed (hello #:key typed-world) (string? #:key optional-s= tring?)
=E2=94=82=C2=A0 =C2=A0(or typed-world "world"))
=E2=94=82 (hello)
=E2=94=82 ;; =3D> "world"
=E2=94=82 (hello #:typed-world "typed")
=E2=94=82 ;; =3D> "typed"
=E2=94=82 (hello #:typed-world #t)
=E2=94=82 ;; =3D> type error ~a ~a #<procedure optional-string? (x)&g= t; #t
=E2=94=82 ;; optional arguments
=E2=94=82 (define-typed (hello #:optional typed-world) (string? #:optional = optional-string?)
=E2=94=82=C2=A0 =C2=A0(or typed-world "world"))
=E2=94=82 (hello)
=E2=94=82 ;; =3D> "world"
=E2=94=82 (hello "typed")
=E2=94=82 ;; =3D> "typed"
=E2=94=82 (hello #t)
=E2=94=82 ;; =3D> type error ~a ~a #<procedure optional-string? (x)&g= t; #t
=E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80

Best wishes,
Arne


--
Patrick: Are they laughing at us?
Sponge Bob: No, Patric= k, they are laughing next to us.
=C2=A0

--0000000000009639030618ed9a0e--