guile-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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


From: Damien Mattei
Subject: Re: define-typed: checking values on proc entry and exit
Date: Tue, 21 May 2024 09:05:37 +0200

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
>


reply via email to

[Prev in Thread] Current Thread [Next in Thread]