hash
, methods overhaul, interfaces again ¶More work in the ‘typed-language’ branch of Vicare, for both the expander and the built–in types infrastructure.
Everything I discuss here is relative to code in the head of the ‘typed-language’ branch.
hash
¶Every object–type in Vicare provides a hash function; the core object–types have
a default hash function, while record–types have a customisable hash function. A
hash value can be computed with the new syntax hash
, with the use template:
(hash ?expr)
If the expander can determine the type of the value returned by ?expr: the syntax use is expanded into an application of the appropriate hash function. Otherwise, the type of the returned value is determined at run–time and the appropriate hash function retrieved and applied. If ?expr does not return a single value: an exception is raised.
For coherence with define-record-type
: the syntax define-label
has
been renamed to define-label-type
; the syntax define-interface
has
been renamed to define-interface-type
; the syntax define-mixin
has
been renamed to define-mixin-type
. Strictly speaking: mixins are not a
type, but still that is the syntax name.
Up to some commits ago, all the syntaxes supporting method specifications (like
define-record-type
) accepted the clauses method
,
case-method
and method/overload
to define methods. I changed this
so that the only clause is now method
.
The clause case-method
defined a typed function with case-define
,
having multiple lambda clauses; it is clear that the same result can be achieved with
an overloaded method having a set of specialisations. So the case-method
has been removed.
Whenever a method with a specific name is defined only once: the new method
clause will define a normal method implementation function, using define
.
Whenever a method with a specific name is defined multiple times: the new
method
clause will act like the old method/overload
clause,
defining an overloaded function with define/overload
.
The previous, experimental, implementation of interfaces has been overhauled; I dare to say that I am close to getting it right. Time for some good copy and paste from the documentation. Here we go…
Let’s illustrate the features and limitations of interface–types by examples.
Whenever an interface inherits from another interface, it obviously becomes its sub–type:
(define-interface-type <IParent> (method-prototype doit (lambda (<number>) => (<string>)))) (define-interface-type <IChild> (parent <IParent>)) (type-annotation-super-and-sub? <IParent> <IChild>) ⇒ #t (type-annotation-super-and-sub? <IChild> <IParent>) ⇒ #f
An interface can implement another interface; the implementer must declare a matching method prototype for every method prototype of the implemented. The method prototype of the implementer must be a sub–type of the method prototype of the implemented. Given that:
(type-annotation-super-and-sub? (lambda (<nestring>) => (<number>)) (lambda (<string>) => (<fixnum>))) ⇒ #t
we can define:
(define-interface-type <IOne> (method-prototype doit (lambda (<nestring>) => (<number>)))) (define-interface-type <ITwo> (implements <IOne>) (method-prototype doit (lambda (<string>) => (<fixnum>)))) (type-annotation-super-and-sub? <IOne> <ITwo>) ⇒ #t (type-annotation-super-and-sub? <ITwo> <IOne>) ⇒ #f
An interface can inherit method prototypes from another interface and use those to implement another interface:
(define-interface-type <I> (method-prototype doit (lambda (<string>) => (<number>)))) (define-interface-type <A> (method-prototype doit (lambda (<string>) => (<number>)))) (define-interface-type <B> (parent <A>) (implements <I>)) (type-annotation-super-and-sub? <I> <A>) ⇒ #f (type-annotation-super-and-sub? <I> <B>) ⇒ #t (type-annotation-super-and-sub? <A> <I>) ⇒ #f (type-annotation-super-and-sub? <B> <I>) ⇒ #f
If the interface <ISub>
inherits from <ISuper>
, to implement
<ISub>
the interface <A>
has to declare a method for every
method in <ISub>
and <ISuper>
:
(define-interface-type <ISuper> (method-prototype super-doit (lambda (<string>) => (<number>)))) (define-interface-type <ISub> (parent <ISuper>) (method-prototype sub-doit (lambda (<string>) => (<fixnum>)))) (define-interface-type <A> (implements <ISub>) (method-prototype super-doit (lambda (<string>) => (<number>))) (method-prototype sub-doit (lambda (<string>) => (<fixnum>)))) (type-annotation-super-and-sub? <ISuper> <A>) ⇒ #t (type-annotation-super-and-sub? <ISub> <A>) ⇒ #t (type-annotation-super-and-sub? <A> <ISuper>) ⇒ #f (type-annotation-super-and-sub? <A> <ISub>) ⇒ #f
Interface <B>
implements interface <A>
; interface <C>
implements interface <B>
; automatically, interface <C>
implements interface <A>
:
(define-interface-type <A> (method-prototype red (lambda () => (<fixnum>)))) (define-interface-type <B> (implements <A>) (method-prototype red (lambda () => (<fixnum>))) (method-prototype blue (lambda () => (<symbol>)))) (define-interface-type <C> (implements <B>) (method-prototype red (lambda () => (<fixnum>))) (method-prototype blue (lambda () => (<symbol>)))) (type-annotation-super-and-sub? <A> <B>) ⇒ #t (type-annotation-super-and-sub? <A> <C>) ⇒ #t (type-annotation-super-and-sub? <B> <A>) ⇒ #f (type-annotation-super-and-sub? <B> <C>) ⇒ #t (type-annotation-super-and-sub? <C> <A>) ⇒ #f (type-annotation-super-and-sub? <C> <B>) ⇒ #f
An interface that inherits from another interface can “extend” its
method prototypes with additional signatures. The interface
<IThree>
implements the interfaces <IOne>
and <ITwo>
.
<IThree>
implements the “composite” method from <IOne>
and
<ITwo>
with multiple method-prototype
clauses:
;; ;; <IOne> ;; ^ ;; | ;; <IThree> +++> <ITwo> ;; (define-interface-type <IOne> (method-prototype doit (lambda (<fixnum>) => (<string>)))) (define-interface-type <ITwo> (parent <IOne>) (method-prototype doit (lambda (<flonum>) => (<string>)))) (define-interface-type <IThree> (implements <ITwo>) (method-prototype doit (lambda (<fixnum>) => (<string>))) (method-prototype doit (lambda (<flonum>) => (<string>)))) (type-annotation-super-and-sub? <IOne> <IThree>) ⇒ #t (type-annotation-super-and-sub? <IOne> <ITwo>) ⇒ #t (type-annotation-super-and-sub? <ITwo> <IThree>) ⇒ #t (type-annotation-super-and-sub? <IThree> <IOne>) ⇒ #f (type-annotation-super-and-sub? <ITwo> <IOne>) ⇒ #f (type-annotation-super-and-sub? <IThree> <ITwo>) ⇒ #f
the same as above, but with a single method-prototype
clause:
(define-interface-type <IOne> (method-prototype doit (lambda (<fixnum>) => (<string>)))) (define-interface-type <ITwo> (parent <IOne>) (method-prototype doit (lambda (<flonum>) => (<string>)))) (define-interface-type <IThree> (implements <ITwo>) (method-prototype doit (case-lambda ((<fixnum>) => (<string>)) ((<flonum>) => (<string>)))))
A record–type can implement an interface by defining a concrete method for every method prototype:
(define-interface-type <IOne> (method-prototype doit (lambda (<string>) => (<number>)))) (define-record-type <blue> (implements <IOne>) (method ({doit <number>} {S <string>}) 1)) (type-annotation-super-and-sub? <IOne> <blue>) ⇒ #t (type-annotation-super-and-sub? <blue> <IOne>) ⇒ #f
A record–type can inherit from another record–type and use its parent’s methods to implement an interface:
(define-interface-type <I> (method-prototype doit (lambda (<string>) => (<number>)))) (define-record-type <A> (method ({doit <number>} {S <string>}) 1)) (define-record-type <B> (parent <A>) (implements <I>)) (type-annotation-super-and-sub? <I> <A>) ⇒ #f (type-annotation-super-and-sub? <I> <B>) ⇒ #t (type-annotation-super-and-sub? <A> <I>) ⇒ #f (type-annotation-super-and-sub? <B> <I>) ⇒ #f
A record–type must implement a method for every method prototype in the interface and its parents:
(define-interface-type <ISuper> (method-prototype super-doit (lambda (<string>) => (<number>)))) (define-interface-type <ISub> (parent <ISuper>) (method-prototype sub-doit (lambda (<string>) => (<fixnum>)))) (define-record-type <A> (implements <ISub>) (method ({super-doit <number>} {S <string>}) 1) (method ({sub-doit <fixnum>} {S <string>}) 1)) (type-annotation-super-and-sub? <ISuper> <A>) ⇒ #t (type-annotation-super-and-sub? <ISub> <A>) ⇒ #t (type-annotation-super-and-sub? <A> <ISuper>) ⇒ #f (type-annotation-super-and-sub? <A> <ISub>) ⇒ #f
The record–type <C>
implements interface <B>
; the interface
<B>
implements interface <A>
; automatically, <C>
implements <A>
:
(define-interface-type <A> (method-prototype red (lambda () => (<fixnum>)))) (define-interface-type <B> (implements <A>) (method-prototype red (lambda () => (<fixnum>))) (method-prototype blue (lambda () => (<symbol>)))) (define-record-type <C> (implements <B>) (method ({red <fixnum>}) 1) (method ({blue <symbol>}) 'ciao)) (type-annotation-super-and-sub? <A> <B>) ⇒ #t (type-annotation-super-and-sub? <A> <C>) ⇒ #t (type-annotation-super-and-sub? <B> <A>) ⇒ #f (type-annotation-super-and-sub? <B> <C>) ⇒ #t (type-annotation-super-and-sub? <C> <A>) ⇒ #f (type-annotation-super-and-sub? <C> <B>) ⇒ #f
The record–type <dark-blue>
inherits from <blue>
the
implementation of the interfaces <IOne>
and <ITwo>
:
;; ;; <IOne> ;; ^ ;; | ;; <blue> +++> <ITwo> ;; ^ ;; | ;; <dark-blue> (define-interface-type <IOne> (method-prototype ione-doit (lambda () => (<number>)))) (define-interface-type <ITwo> (parent <IOne>) (method-prototype itwo-doit (lambda () => (<symbol>)))) (define-record-type <blue> (implements <ITwo>) (fields val) (method ({ione-doit <number>}) (+ 10 (.val this))) (method ({itwo-doit <symbol>}) 'ciao)) (define-record-type <dark-blue> (parent <blue>)) (define (fun-1 {O <IOne>}) (.ione-doit O)) (define (fun-2 {O <ITwo>}) (vector (.ione-doit O) (.itwo-doit O))) (define O (new <dark-blue> 1)) (fun-1 O) ⇒ 11 (fun-2 O) ⇒ #(11 ciao)
The record–type <dark-blue>
implements the interface <ITwo>
and inherits from <blue>
the implementation of the interface
<IOne>
:
;; <blue> +++> <IOne> ;; ^ ;; | ;; <dark-blue> +++> <ITwo> (define-interface-type <IOne> (method-prototype ione-doit (lambda () => (<number>)))) (define-interface-type <ITwo> (method-prototype itwo-doit (lambda () => (<symbol>)))) (define-record-type <blue> (implements <IOne>) (fields val) (method ({ione-doit <number>}) (+ 10 (.val this)))) (define-record-type <dark-blue> (parent <blue>) (implements <ITwo>) (method ({itwo-doit <symbol>}) 'ciao)) (define (fun-1 {O <IOne>}) (.ione-doit O)) (define (fun-2 {O <ITwo>}) (.itwo-doit O)) (define O (new <dark-blue> 1)) (fun-1 O) ⇒ 11 (fun-2 O) ⇒ ciao
The record–type <blue>
implements the interface <ITwo>
, its
parent <IOne>
, and automatically the interface <IThree>
implemented by <IOne>
:
;; <IOne> +++> <IThree> ;; ^ ;; | ;; <blue> +++> <ITwo> (define-interface-type <IThree> (method-prototype ithree-doit (lambda () => (<string>)))) (define-interface-type <IOne> (implements <IThree>) (method-prototype ione-doit (lambda () => (<number>))) (method-prototype ithree-doit (lambda () => (<string>)))) (define-interface-type <ITwo> (parent <IOne>) (method-prototype itwo-doit (lambda () => (<symbol>)))) (define-record-type <blue> (implements <ITwo>) (fields val) (method ({ione-doit <number>}) (+ 10 (.val this))) (method ({itwo-doit <symbol>}) 'ciao) (method ({ithree-doit <string>}) "hello")) (define (fun-1 {O <IOne>}) (vector (.ione-doit O) (.ithree-doit O))) (define (fun-2 {O <ITwo>}) (vector (.ione-doit O) (.itwo-doit O) (.ithree-doit O))) (define (fun-3 {O <IThree>}) (.ithree-doit O)) (define O (new <blue> 1)) (fun-1 O) ⇒ #(11 "hello") (fun-2 O) ⇒ #(11 ciao "hello") (fun-3 O) ⇒ "hello"
Two record–types in a hierarchy both implement the same interface:
(define-interface-type <Arith> (method-prototype add (lambda () => (<number>)))) (define-record-type <duo> (implements <Arith>) (fields one two) (method ({add <number>}) (+ (.one this) (.two this)))) (define-record-type <trio> (parent <duo>) (implements <Arith>) (fields three) (method ({add <number>}) (+ (.one this) (.two this) (.three this)))) (define (fun {O <Arith>}) (.add O)) (fun (new <duo> 1 2)) ⇒ 3 (fun (new <trio> 1 2 3)) ⇒ 6
The record–type <duo>
implements the interface <Stringer>
which has a default method to-string
:
(define-interface-type <Stringer> (method (to-string) (with-output-to-string (lambda () (display this))))) (define-record-type <duo> (implements <Stringer>) (fields one two) (custom-printer (lambda ({this <duo>} port sub-printer) (display "#[duo " port) (display (.one this) port) (display #\space port) (display (.two this) port) (display #\] port)))) (define (fun {O <Stringer>}) (.to-string O)) (fun (new <duo> 1 2)) ⇒ "#[duo 1 2]"
The record–type <duo>
implements the interface <Stringer>
which has a default method to-string
; <duo>
implements the
method by itself:
(define-interface-type <Stringer> (method ({to-string <string>}) (with-output-to-string (lambda () (display this))))) (define-record-type <duo> (implements <Stringer>) (fields one two) (method ({to-string <string>}) (with-output-to-string (lambda () (display "#[duo ") (display (.one this)) (display #\space) (display (.two this)) (display #\]))))) (define (fun {O <Stringer>}) (.to-string O)) (fun (new <duo> 1 2)) ⇒ "#[duo 1 2]"
Default methods have a limitation: they cannot extend other methods. The following definitions will raise an expand–time exception:
(define-interface-type <IOne> (method ({doit <number>}) 1)) (define-interface-type <ITwo> (parent <IOne>) (method-prototype doit (lambda (<string>) => (<number>))))
because we cannot extend the default method doit
in <IOne>
with a method prototype in <ITwo>
. The following definitions will
raise an expand–time exception:
(define-interface-type <IOne> (method-prototype doit (lambda (<string>) => (<number>)))) (define-interface-type <ITwo> (parent <IOne>) (method ({doit <number>}) 1))
because we cannot extend the method prototype doit
in
<IOne>
with a default method in <ITwo>
. The following
definitions will raise an expand–time exception:
(define-interface-type <IOne> (method ({doit <number>}) 1)) (define-interface-type <ITwo> (parent <IOne>) (method ({doit <number>} {S <string>}) 2))
because we cannot extend the default method doit
in <IOne>
with the default method doit
in <ITwo>
.
Here we use an “instantiable body” to define a “generic” interface–type:
(import (vicare language-extensions instantiable-bodies)) (define-instantiable-body define-iface-arith (define-interface-type <Iface> (method-prototype add (lambda () => (<type-name>))))) (begin (define-iface-arith ((<Iface> <NumberArith>) (<type-name> <number>))) (define (nfun {O <NumberArith>}) (.add O))) (begin (define-iface-arith ((<Iface> <StringArith>) (<type-name> <string>))) (define (sfun {O <StringArith>}) (.add O))) (define-record-type <duo> (implements <NumberArith>) (fields one two) (method ({add <number>}) (+ (.one this) (.two this)))) (define-record-type <string-duo> (implements <StringArith>) (fields one two) (method ({add <string>}) (string-append (.one this) (.two this)))) (nfun (new <duo> 1 2)) ⇒ 3 (sfun (new <string-duo> "hel" "lo")) ⇒ "hello"