unofficial mirror of guile-user@gnu.org 
 help / color / mirror / Atom feed
* define-typed: checking values on proc entry and exit
@ 2024-05-10  7:47 Dr. Arne Babenhauserheide
  2024-05-10 22:49 ` Zelphir Kaltstahl
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: Dr. Arne Babenhauserheide @ 2024-05-10  7:47 UTC (permalink / raw)
  To: guile-user

[-- Attachment #1: Type: text/plain, Size: 3381 bytes --]

Hi,

in #guile on IRC¹, old talked about Typed Racket so I thought whether
that could be done with define-syntax-rule. So I created define-typed.
This is also on my website², but I wanted to share and discuss it here.

I follow the format by [sph-sc], a Scheme to C compiler. It declares
types after the function definition like this:

┌────
│ (define (hello typed-world) (string? string?)
│   typed-world)
└────

┌────
│ (define-syntax-rule (define-typed (procname args ...) (ret? types ...) body ...)
│   (begin
│     (define (procname args ...)
│       ;; define a helper pro
│       (define (helper)
│         body ...)
│       ;; use a typecheck prefix for the arguments
│       (map (λ (type? argument)
│              (unless (type? argument)
│                (error "type error ~a ~a" type? argument)))
│            (list types ...) (list args ...) )
│       ;; get the result
│       (let ((res (helper)))
│         ;; typecheck the result
│         (unless (ret? res)
│           (error "type error: return value ~a does not match ~a"
│                  res ret?))
│         ;; return the result
│         res))
│     ;; add procedure properties
│     (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, and so forth.

┌────
│ (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"
│   #((props))
│   typed-world)
│ (procedure-properties hello)
│ ;; => ((name . hello) (documentation . "typed") (props))
└────

This should automate some of the guards of [Optimizing Guile Scheme], so
the compiler can optimize more (i.e. if you check for `real?') but keep
in mind that these checks are not free: only typecheck outside tight
loops.

They provide a type boundary instead of forcing explicit static typing.

Also you can do more advanced checks by providing your own test
procedures and validating your API more elegantly, but these then won’t
help the compiler produce faster code.

But keep in mind that this does not actually provide static program
analysis like while-you-write type checks. It’s simply [syntactic sugar]
for a boundary through which only allowed values can pass. Thanks to
program flow analysis by the just-in-time compiler, it can make your
code faster, but that’s not guaranteed. It may be useful for your next
API definition.

My question now: do you have an idea for a better name than
define-typed?


[sph-sc] <https://github.com/sph-mn/sph-sc>

[Optimizing Guile Scheme]
<https://dthompson.us/posts/optimizing-guile-scheme.html>

[syntactic sugar]
<http://www.phyast.pitt.edu/~micheles/scheme/scheme12.html#are-macros-just-syntactic-sugar>


¹ https://web.libera.chat/?nick=Wisp|?#guile
² https://www.draketo.de/software/guile-snippets#define-typed


Best wishes,
Arne

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-10  7:47 define-typed: checking values on proc entry and exit Dr. Arne Babenhauserheide
@ 2024-05-10 22:49 ` Zelphir Kaltstahl
  2024-05-14  1:39   ` Dr. Arne Babenhauserheide
  2024-05-11  2:48 ` Olivier Dion
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 11+ messages in thread
From: Zelphir Kaltstahl @ 2024-05-10 22:49 UTC (permalink / raw)
  To: Dr. Arne Babenhauserheide; +Cc: Guile User

On 10.05.24 15:47, Dr. Arne Babenhauserheide wrote:
> Hi,
>
> in #guile on IRC¹, old talked about Typed Racket so I thought whether
> that could be done with define-syntax-rule. So I created define-typed.
> This is also on my website², but I wanted to share and discuss it here.
>
> I follow the format by [sph-sc], a Scheme to C compiler. It declares
> types after the function definition like this:
>
> ┌────
> │ (define (hello typed-world) (string? string?)
> │   typed-world)
> └────
>
> ┌────
> │ (define-syntax-rule (define-typed (procname args ...) (ret? types ...) body ...)
> │   (begin
> │     (define (procname args ...)
> │       ;; define a helper pro
> │       (define (helper)
> │         body ...)
> │       ;; use a typecheck prefix for the arguments
> │       (map (λ (type? argument)
> │              (unless (type? argument)
> │                (error "type error ~a ~a" type? argument)))
> │            (list types ...) (list args ...) )
> │       ;; get the result
> │       (let ((res (helper)))
> │         ;; typecheck the result
> │         (unless (ret? res)
> │           (error "type error: return value ~a does not match ~a"
> │                  res ret?))
> │         ;; return the result
> │         res))
> │     ;; add procedure properties
> │     (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, and so forth.
>
> ┌────
> │ (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"
> │   #((props))
> │   typed-world)
> │ (procedure-properties hello)
> │ ;; => ((name . hello) (documentation . "typed") (props))
> └────
>
> This should automate some of the guards of [Optimizing Guile Scheme], so
> the compiler can optimize more (i.e. if you check for `real?') but keep
> in mind that these checks are not free: only typecheck outside tight
> loops.
>
> They provide a type boundary instead of forcing explicit static typing.
>
> Also you can do more advanced checks by providing your own test
> procedures and validating your API more elegantly, but these then won’t
> help the compiler produce faster code.
>
> But keep in mind that this does not actually provide static program
> analysis like while-you-write type checks. It’s simply [syntactic sugar]
> for a boundary through which only allowed values can pass. Thanks to
> program flow analysis by the just-in-time compiler, it can make your
> code faster, but that’s not guaranteed. It may be useful for your next
> API definition.
>
> My question now: do you have an idea for a better name than
> define-typed?
>
>
> [sph-sc] <https://github.com/sph-mn/sph-sc>
>
> [Optimizing Guile Scheme]
> <https://dthompson.us/posts/optimizing-guile-scheme.html>
>
> [syntactic sugar]
> <http://www.phyast.pitt.edu/~micheles/scheme/scheme12.html#are-macros-just-syntactic-sugar>
>
>
> ¹ https://web.libera.chat/?nick=Wisp|?#guile
> ² https://www.draketo.de/software/guile-snippets#define-typed
>
>
> Best wishes,
> Arne

Hello Arne,

You might also be interested in my repository for function contracts using CK 
macros:

https://codeberg.org/ZelphirKaltstahl/guile-examples/src/commit/0e231c289596cb4c445efb30168105914a8539a5/macros/contracts

I hope I can check out your macro soon and hope to be able to understand it : )

Regards,
Zelphir

-- 
repositories: https://notabug.org/ZelphirKaltstahl




^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-10  7:47 define-typed: checking values on proc entry and exit Dr. Arne Babenhauserheide
  2024-05-10 22:49 ` Zelphir Kaltstahl
@ 2024-05-11  2:48 ` Olivier Dion
  2024-05-13  9:51   ` Ricardo Wurmus
  2024-05-14  1:15   ` Dr. Arne Babenhauserheide
  2024-05-11  8:18 ` Vivien Kraus
  2024-05-21  7:05 ` Damien Mattei
  3 siblings, 2 replies; 11+ messages in thread
From: Olivier Dion @ 2024-05-11  2:48 UTC (permalink / raw)
  To: Dr. Arne Babenhauserheide, guile-user

On Fri, 10 May 2024, "Dr. Arne Babenhauserheide" <arne_bab@web.de> wrote:

This is entirely off topic, sorry, but how do you do the following in Emacs:

> ┌────
> │ ...
> └────

I usualy use:

--8<---------------cut here---------------start------------->8---
...
--8<---------------cut here---------------end--------------->8---

but I found your format to be easier to read.

-- 
Olivier Dion
oldiob.ca



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-10  7:47 define-typed: checking values on proc entry and exit Dr. Arne Babenhauserheide
  2024-05-10 22:49 ` Zelphir Kaltstahl
  2024-05-11  2:48 ` Olivier Dion
@ 2024-05-11  8:18 ` Vivien Kraus
  2024-05-14  1:34   ` Dr. Arne Babenhauserheide
  2024-05-21  7:05 ` Damien Mattei
  3 siblings, 1 reply; 11+ messages in thread
From: Vivien Kraus @ 2024-05-11  8:18 UTC (permalink / raw)
  To: Dr. Arne Babenhauserheide, guile-user

Hello!

This is an interesting approach, thank you.

Le vendredi 10 mai 2024 à 09:47 +0200, Dr. Arne Babenhauserheide a
écrit :
> │       ;; get the result
> │       (let ((res (helper)))
> │         ;; typecheck the result
> │         (unless (ret? res)
> │           (error "type error: return value ~a does not match ~a"
> │                  res ret?))
> │         ;; return the result
> │         res))

A nice improvement would be to support multiple return values, for
instance by using call-with-values, and checking the return value with
(apply ret? res) instead of (ret? res).

What do you think?

Best regards,

Vivien

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-11  2:48 ` Olivier Dion
@ 2024-05-13  9:51   ` Ricardo Wurmus
  2024-05-14  1:15   ` Dr. Arne Babenhauserheide
  1 sibling, 0 replies; 11+ messages in thread
From: Ricardo Wurmus @ 2024-05-13  9:51 UTC (permalink / raw)
  To: Olivier Dion; +Cc: Dr. Arne Babenhauserheide, guile-user

Olivier Dion <olivier.dion@polymtl.ca> writes:

> On Fri, 10 May 2024, "Dr. Arne Babenhauserheide" <arne_bab@web.de> wrote:
>
> This is entirely off topic, sorry, but how do you do the following in Emacs:
>
>> ┌────
>> │ ...
>> └────
>
> I usualy use:
>
> --8<---------------cut here---------------start------------->8---
> ...
> --8<---------------cut here---------------end--------------->8---
>
> but I found your format to be easier to read.

The cut markers are inserted by "message-mark-inserted-region" (from
message.el), which uses the customizable "message-mark-insert-end" and
"message-mark-insert-begin".  It does not provide a string to prepend to
each line, though, so you'd need to wrap "message-mark-inserted-region".

-- 
Ricardo



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-11  2:48 ` Olivier Dion
  2024-05-13  9:51   ` Ricardo Wurmus
@ 2024-05-14  1:15   ` Dr. Arne Babenhauserheide
  1 sibling, 0 replies; 11+ messages in thread
From: Dr. Arne Babenhauserheide @ 2024-05-14  1:15 UTC (permalink / raw)
  To: Olivier Dion

[-- Attachment #1: Type: text/plain, Size: 794 bytes --]

Olivier Dion <olivier.dion@polymtl.ca> writes:

> On Fri, 10 May 2024, "Dr. Arne Babenhauserheide" <arne_bab@web.de> wrote:
>
> This is entirely off topic, sorry, but how do you do the following in Emacs:
>
>> ┌────
>> │ ...
>> └────
>
> I usualy use:
>
> --8<---------------cut here---------------start------------->8---
> ...
> --8<---------------cut here---------------end--------------->8---
>
> but I found your format to be easier to read.

I wrote that code example in org-mode and used export to text ⇒ unicode.

That came naturally, because it’s how I write my website:
https://www.draketo.de/software/guile-snippets#define-typed

source: https://hg.sr.ht/~arnebab/draketo/browse/software/guile-snippets.org#L394

Best wishes,
Arne

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-11  8:18 ` Vivien Kraus
@ 2024-05-14  1:34   ` Dr. Arne Babenhauserheide
  0 siblings, 0 replies; 11+ messages in thread
From: Dr. Arne Babenhauserheide @ 2024-05-14  1:34 UTC (permalink / raw)
  To: Vivien Kraus

[-- Attachment #1: Type: text/plain, Size: 2798 bytes --]

Vivien Kraus <vivien@planete-kraus.eu> writes:

> Hello!
>
> This is an interesting approach, thank you.
>
> Le vendredi 10 mai 2024 à 09:47 +0200, Dr. Arne Babenhauserheide a
> écrit :
>> │       ;; get the result
>> │       (let ((res (helper)))
>> │         ;; typecheck the result
>> │         (unless (ret? res)
>> │           (error "type error: return value ~a does not match ~a"
>> │                  res ret?))
>> │         ;; return the result
>> │         res))
>
> A nice improvement would be to support multiple return values, for
> instance by using call-with-values, and checking the return value with
> (apply ret? res) instead of (ret? res).
>
> What do you think?

That’s a nice idea!

With some experimentation I got it working:

┌────
│ (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)
│              (unless (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)))
│     ;; 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!), and so forth.

┌────
│ (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"
│   #((props))
│   typed-world)
│ (procedure-properties hello)
│ ;; => ((name . hello) (documentation . "typed") (props))
│ (define-typed (multiple-values num) ((λ(a b) (> a b)) number?)
│   (values (* 2 (abs num)) num))
│ (multiple-values -3)
│ ;; => 6
│ ;; => -3
└────


Best wishes,
Arne

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-10 22:49 ` Zelphir Kaltstahl
@ 2024-05-14  1:39   ` Dr. Arne Babenhauserheide
  2024-05-15  0:10     ` Dr. Arne Babenhauserheide
  0 siblings, 1 reply; 11+ messages in thread
From: Dr. Arne Babenhauserheide @ 2024-05-14  1:39 UTC (permalink / raw)
  To: Zelphir Kaltstahl

[-- Attachment #1: Type: text/plain, Size: 761 bytes --]

Zelphir Kaltstahl <zelphirkaltstahl@posteo.de> writes:

> On 10.05.24 15:47, Dr. Arne Babenhauserheide wrote:
> You might also be interested in my repository for function contracts
> using CK macros:
>
> https://codeberg.org/ZelphirKaltstahl/guile-examples/src/commit/0e231c289596cb4c445efb30168105914a8539a5/macros/contracts
>
> I hope I can check out your macro soon and hope to be able to understand it : )

These sound pretty powerful — thank you for sharing!

I went for the simplest option that I thought could work, and CK-macros
seem to be much bigger (from skimming the code).

And the *-versions are ominous: optional and keyword arguments may be
the next frontier.

I’m not sure how to keep those simple.

Best wishes,
Arne

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-14  1:39   ` Dr. Arne Babenhauserheide
@ 2024-05-15  0:10     ` Dr. Arne Babenhauserheide
  2024-05-21  2:30       ` Linas Vepstas
  0 siblings, 1 reply; 11+ messages in thread
From: Dr. Arne Babenhauserheide @ 2024-05-15  0:10 UTC (permalink / raw)
  To: guile-user, Zelphir Kaltstahl, guile-devel

[-- Attachment #1: Type: text/plain, Size: 4346 bytes --]

"Dr. Arne Babenhauserheide" <arne_bab@web.de> writes:

> Zelphir Kaltstahl <zelphirkaltstahl@posteo.de> 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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-15  0:10     ` Dr. Arne Babenhauserheide
@ 2024-05-21  2:30       ` Linas Vepstas
  0 siblings, 0 replies; 11+ messages in thread
From: Linas Vepstas @ 2024-05-21  2:30 UTC (permalink / raw)
  To: Dr. Arne Babenhauserheide; +Cc: guile-user, Zelphir Kaltstahl, guile-devel

[-- Attachment #1: Type: text/plain, Size: 5263 bytes --]

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 PM Dr. Arne Babenhauserheide <arne_bab@web.de>
wrote:

> "Dr. Arne Babenhauserheide" <arne_bab@web.de> writes:
>
> > Zelphir Kaltstahl <zelphirkaltstahl@posteo.de> 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
>


-- 
Patrick: Are they laughing at us?
Sponge Bob: No, Patrick, they are laughing next to us.

[-- Attachment #2: Type: text/html, Size: 6874 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: define-typed: checking values on proc entry and exit
  2024-05-10  7:47 define-typed: checking values on proc entry and exit Dr. Arne Babenhauserheide
                   ` (2 preceding siblings ...)
  2024-05-11  8:18 ` Vivien Kraus
@ 2024-05-21  7:05 ` Damien Mattei
  3 siblings, 0 replies; 11+ messages in thread
From: Damien Mattei @ 2024-05-21  7:05 UTC (permalink / raw)
  To: Dr. Arne Babenhauserheide; +Cc: guile-user

Hello,

not sure to understand but does GOOPS (Guile object model) allow some sort
of check to the argument but perhaps it is only inherent to the procedures
of a 'class' in the object model?

anyway if you are just interesting in type checking some procedure Scheme+
(ref: 1) that i currently still developing allow this when overloading
procedures and operators as you specify a type as you by a list of
predicates example: (number? number?) but rather than you i do not specify
a return type as there is no way to check the value the procedure (example
: (set! x (foo y)) will set will be put of a variable of same type as
scheme is not typed (here who know the type of x?)
If you call the overloaded procedure with not "declared" arguments it will
display an error.

It works both for procedure and "operators" even it is the same in scheme
the implementation are different.

it works like this for example:

overloading some existing procedures:

(use-modules (overload))

i overload length to accept both vector and string and still lists:

(overload-existing-procedure length vector-length (vector?))
(overload-existing-procedure length string-length (string?))

use:

scheme@(guile-user)> (length "abcde")
$3 = 5
scheme@(guile-user)> (length '(1 2 3 4))
$4 = 4
scheme@(guile-user)> (length #(1 2 3))
$5 = 3


overload a non (already) existing procedure, i need to do it in 2 steps:
first say i will define it later:
(define-overload-procedure area)
perheaps i should rename this declare-... instead of define-...

then define the new procedures to compute area of square and rectangle:
(define (surf-carre-parfait x) (* x x))
(define (surf-carre-long x y) (* x y))

note: in old Français a "carré long" (long square) is what we call today a
rectangle :-)

(overload-procedure area surf-carre-parfait (number?))
(overload-procedure area surf-carre-long (number? number?))

tests:
scheme@(guile-user)>  (area 3.4)
$1 = 11.559999999999999
scheme@(guile-user)> (area 2.3 4.5)
$2 = 10.35
scheme@(guile-user)> (area "hello")
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
overload "failed because procedure can not be applied to arguments list.
procedure , arguments list = " *b ("hello")


overloading an operator, for the sum of n lists:

first define a procedure doing the job because i need to know it before
overloading +:
(define (add-n-lists . vn-lst) (implementation-add-n-lists vn-lst))
note here add-n-lists is just a wrapper to implementation-add-n-lists which
will be defined later....

then use the overloading macro to overload the + operator:
(overload-existing-n-arity-operator + add-n-lists (list? list?))

then of course implement really the sum of n lists:
(define (implementation-add-n-lists vn-lst)
  {map-args <+ (cons + vn-lst)}
  (apply map map-args))

or :
(define (implementation-add-n-lists vn-lst)
  (define map-args (cons + vn-lst))
  (apply map map-args))

another example that multiply all elements of a list by a number:

(define (mult-num-list k v) (map (λ (x) (* k x)) v))

(overload-existing-operator * mult-num-list (number? list?))

the system in action:

(define t {3 * '(1 2 3) + '(4 5 6) + '(7 8 9)})
(display t) (newline)

will give:

(14 19 24)

note: as * has precedence over + we first evaluate 3 * '(1 2 3) = '(3 6 9)
and after add  '(4 5 6) + '(7 8 9)

i will not explain the implementation here, it is too long , it is at the
limit of what scheme can do, for this reason implementation can differ from
a scheme to another (i could use hash table in some implementation for
example...and not with another scheme - racket is more strict) , it will
work at the toplevel of a module and at REPL but for now you could not
overload in a function - not interesting in fact) and because of the
operator precedence, but all is in respect of scheme syntax rules, i'm
uniformising between schemes.Things could be hard if you want to overload [
] with SRFI 105 but it's work.

The last stable version of code is available here :
https://github.com/damien-mattei/Scheme-PLUS-for-Guile/blob/main/overload.scm

1 : https://github.com/damien-mattei/Scheme-PLUS-for-Guile/

Regard,

Damien


On Fri, May 10, 2024 at 9:48 AM Dr. Arne Babenhauserheide <arne_bab@web.de>
wrote:

> Hi,
>
> in #guile on IRC¹, old talked about Typed Racket so I thought whether
> that could be done with define-syntax-rule. So I created define-typed.
> This is also on my website², but I wanted to share and discuss it here.
>
> I follow the format by [sph-sc], a Scheme to C compiler. It declares
> types after the function definition like this:
>
> ┌────
> │ (define (hello typed-world) (string? string?)
> │   typed-world)
> └────
>
> ┌────
> │ (define-syntax-rule (define-typed (procname args ...) (ret? types ...)
> body ...)
> │   (begin
> │     (define (procname args ...)
> │       ;; define a helper pro
> │       (define (helper)
> │         body ...)
> │       ;; use a typecheck prefix for the arguments
> │       (map (λ (type? argument)
> │              (unless (type? argument)
> │                (error "type error ~a ~a" type? argument)))
> │            (list types ...) (list args ...) )
> │       ;; get the result
> │       (let ((res (helper)))
> │         ;; typecheck the result
> │         (unless (ret? res)
> │           (error "type error: return value ~a does not match ~a"
> │                  res ret?))
> │         ;; return the result
> │         res))
> │     ;; add procedure properties
> │     (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, and so forth.
>
> ┌────
> │ (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"
> │   #((props))
> │   typed-world)
> │ (procedure-properties hello)
> │ ;; => ((name . hello) (documentation . "typed") (props))
> └────
>
> This should automate some of the guards of [Optimizing Guile Scheme], so
> the compiler can optimize more (i.e. if you check for `real?') but keep
> in mind that these checks are not free: only typecheck outside tight
> loops.
>
> They provide a type boundary instead of forcing explicit static typing.
>
> Also you can do more advanced checks by providing your own test
> procedures and validating your API more elegantly, but these then won’t
> help the compiler produce faster code.
>
> But keep in mind that this does not actually provide static program
> analysis like while-you-write type checks. It’s simply [syntactic sugar]
> for a boundary through which only allowed values can pass. Thanks to
> program flow analysis by the just-in-time compiler, it can make your
> code faster, but that’s not guaranteed. It may be useful for your next
> API definition.
>
> My question now: do you have an idea for a better name than
> define-typed?
>
>
> [sph-sc] <https://github.com/sph-mn/sph-sc>
>
> [Optimizing Guile Scheme]
> <https://dthompson.us/posts/optimizing-guile-scheme.html>
>
> [syntactic sugar]
> <
> http://www.phyast.pitt.edu/~micheles/scheme/scheme12.html#are-macros-just-syntactic-sugar
> >
>
>
> ¹ https://web.libera.chat/?nick=Wisp|?#guile
> ² https://www.draketo.de/software/guile-snippets#define-typed
>
>
> Best wishes,
> Arne
>


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2024-05-21  7:05 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-05-10  7:47 define-typed: checking values on proc entry and exit Dr. Arne Babenhauserheide
2024-05-10 22:49 ` Zelphir Kaltstahl
2024-05-14  1:39   ` Dr. Arne Babenhauserheide
2024-05-15  0:10     ` Dr. Arne Babenhauserheide
2024-05-21  2:30       ` Linas Vepstas
2024-05-11  2:48 ` Olivier Dion
2024-05-13  9:51   ` Ricardo Wurmus
2024-05-14  1:15   ` Dr. Arne Babenhauserheide
2024-05-11  8:18 ` Vivien Kraus
2024-05-14  1:34   ` Dr. Arne Babenhauserheide
2024-05-21  7:05 ` Damien Mattei

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).