Previous: interfaces descr, Up: interfaces [Contents][Index]
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"
Previous: interfaces descr, Up: interfaces [Contents][Index]