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.
slot-set
and slot-ref!
constructor-signature
clause for define-record-type
Done a crackdown on the machinery that determines when two expand–time type specifications, or two run–time type descriptors, are super–type and sub–type. There are plenty of built–in syntaxes to check this relation.
Given the core types <number>
, <fixnum>
and <string>
, we have:
(type-annotation-matching <number> <fixnum>) ⇒ exact-match (type-annotation-matching <fixnum> <number>) ⇒ possible-match (type-annotation-matching <number> <string>) ⇒ no-match
it means that the following code will type–check at expand–time, and no type checking is performed at run–time on the operand ‘123’:
(define (fun {O <number>}) O) (fun 123) ⇒ 123
the following code will type–check at expand–time, and the operand in the
application of fun
is checked at run–time:
(define (fun {O <fixnum>}) O) (define ({rand <number>}) 123) (fun (rand))
the following code will raise an expand–time violation:
(define (fun {O <fixnum>}) O) (fun "ciao") error→ expand-time type mismatch
There are many type annotation variants, so there are many rules to compare type specifications and type descriptors. Special care is needed when defining type annotations for closure objects that must be used as operands in function calls. The general rule is this:
(type-annotation-super-and-sub? <number> <fixnum>) ⇒ #t (type-annotation-super-and-sub? <struct> <record>) ⇒ #t (type-annotation-super-and-sub? (lambda (<fixnum>) => (<struct>)) (lambda (<number>) => (<record>))) ⇒ #t
we see that:
<fixnum>
) is a sub–type of the
argument of the sub–type’s closure (<number>
).
<struct>
) is a super–type of
the return value of the sub–type’s closure (<record>
).
in other words: the super–type’s argument must be equal or stricter; the sub–type’s return value must be equal or stricter.
Scheme is, originally, a dynamically typed language; so, sometimes, we may need a type specification describing a closure object that accepts arguments of any type. Given the comparison rules for super–types and sub–types, this appears problematic because the super–type’s arguments must have types that are sub–types of any other type.
For this we can use the special type annotation <bottom>
, which is a
conventional sub–type of all the other types; for example the type annotation:
(define-type <my-func> (lambda (<bottom>) => (<string>)))
is a super–type of both number->string
and symbol->string
, because
<number>
and <symbol>
are super–types of <bottom>
.
In addition, the improper list <bottom>
is now used to represent the type of
return values for expressions that do not return (for example: they raise an
exception). So now:
(type-of error) ⇒ #[signature ((lambda ((or <false> <symbol> <string>) <string> . <list>) => <bottom>))] (type-of (error #f "wrong")) ⇒ #[signature <bottom>]
in previous revisions Vicare used <no-return>
for this purpose, but now
<no-return>
has been removed.
slot-set
and slot-ref!
¶I have removed slot-set
and slot-ref!
; with the typed language’s
dot notation to access record’s and struct’s fields, there is no need to keep such
syntaxes. Also, they say that having a uniform syntax to access an object’s
interface makes code more maintainable
(Uniform access
principle).
constructor-signature
clause for define-record-type
¶I have implemented the constructor-signature
clause to specify the type
signature of record–type constructor procedures:
(define-record-type <alpha> (fields {A <fixnum>} {B <string>}) (protocol (lambda (make-record) (lambda (A) (make-record A (number->string A))))) (constructor-signature (lambda (<fixnum>) => (<alpha>))))