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]