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. In this post I will take great pleasure in just copying and pasting chunks of text from the documentation’s Texinfo source files (life is hard, but sometimes less so).
Vicare already had the <symbol>
type for symbol objects. It now also
supports the type annotation:
(enumeration ?symbol0 ?symbol ...)
enumerations are used to match a symbol in a specified enumeration set:
(is-a? 'ciao (enumeration hello ciao salut ohayo)) ⇒ #t (is-a? 'blue (enumeration hello ciao salut ohayo)) ⇒ #f
Enumeration type annotations are considered sub–types of <symbol>
. We can
combine enumerations in type annotations to filter out some symbols:
(define-type <my-symbols> (and (enumeration red blue green yellow purple) (enumeration blue yellow magenta))) (is-a? 'blue <my-symbols>) ⇒ #t (is-a? 'red <my-symbols>) ⇒ #f
As special case, if we define an alias for an enumeration
type
annotation: we can use such identifier to validate symbols. Example:
(define-type greetings (enumeration hello ciao salut ohayo)) (is-a? 'ciao greetings) ⇒ #t (greetings ciao) ⇒ ciao (greetings blue) error→ symbol not in enumeration
The enumeration identifier is indeed used in the implementation of the
define-enumeration
built–in syntax:
(define-enumeration greetings (hello ciao salut ohayo) make-greetings) (is-a? 'ciao greetings) ⇒ #t (greetings ciao) ⇒ ciao (greetings blue) error→ symbol not in enumeration
I needed to code something fun, so I implemented label types; these entities where already (in a similar form) in the Nausicaa libraries. Labels are types built on top of other types: we put type labels on values of a parent type to handle them locally in a special way. There are two kinds of labels:
Label types are defined using the syntax define-label
, exported by the
library (vicare language-extensions labels)
; labels are implemented half in
the expander itself and half in this external library. A define-label
use
can contain any of the following clauses, much similar to
define-record-type
:
parent type-predicate equality-predicate comparison-procedure hash-function method case-method
where parent
is mandatory; the parent of a label type can be any type
annotation.
The following example defines a label <String>
that is just a synonym for
<string>
:
(define-label <String> (parent <string>)) (define {O <String>} "ciao") (.length O) ⇒ 4 (.hash O) → (string-hash O)
now let’s define a custom hash function (we ignore the parent hash function that gets passed as parent-func argument):
(define-label <String> (parent <string>) (hash-function (lambda (parent-func) (lambda (S) (if (string-empty? S) 0 (char-hash (string-ref S 0))))))) (define {O <String>} "ciao") (.hash O) ≡ (char-hash #\c)
now let’s define a method for appending prefixes and suffixes:
(define-label <String> (parent <string>) (case-method append (({_ <String>} {O <String>} {suff <String>}) (string-append O suff)) (({_ <String>} {O <String>} {pref <String>} {suff <String>}) (string-append pref O suff)))) (define {O <String>} "ciao") (.append O "-suff") ⇒ "ciao-suff" (.append O "pref-" "-suff") ⇒ "pref-ciao-suff" (.length (.append O "pref-" "-suff")) ⇒ 14
Now let’s define a label type to represent fixnums returned by comparison procedures (‘-1’, ‘0’, ‘+1’):
(define-label <comparison-fixnum> (parent (or <non-negative-fixnum> <negative-fixnum>)) (type-predicate (lambda (parent-pred) (lambda (obj) (and (parent-pred obj) (fx<=? obj +1) (fx>=? obj -1)))))) (is-a? +1 <comparison-fixnum>) ⇒ #t (is-a? -1 <comparison-fixnum>) ⇒ #t (is-a? 0 <comparison-fixnum>) ⇒ #t (is-a? +2 <comparison-fixnum>) ⇒ #f (is-a? -2 <comparison-fixnum>) ⇒ #f
I am well aware that, whenever a type error is found by the expander, the displayed error message is hard to read by humans. The message is just a dump of the contents of a compound condition object:
vicare> (display 1 2) Unhandled exception Condition components: 1. &who: chi-application 2. &message: "expand-time mismatch between closure object's arguments signatures and operands signature" 3. &expand-time-type-signature-warning 4. &syntax: form: #[syntax expr=(display 1 2)] subform: #[id display] 5. &application-operator-expression: #[id display] 6. &application-operands-expressions: #[syntax expr=(1 2)] 7. &arguments-signatures: (#[signature (<top>)] #[signature (<top> <textual-output-port>)]) 8. &operands-signature: #[signature (<positive-fixnum> <positive-fixnum>)]
notice that this thing is for a very simple function; it gets much worse with functions having many clauses with many arguments.
In one of the possible futures of the Universe: I will implement a special exception handler in the expander, so that these condition objects are parsed and readable text is printed. It will not be easy because there is a lot of information to be shown…
No there is no such thing, yet. I have read Racket’s one and I have seen Professor Felleisen explain it in a tech talk: it is great that people can state their philosophy in driving a project.
Ahem… at present, the only thing I can say about Vicare is: if you like the Scheme language and procedural macros, give it a try (it will have static typing, too!). Not much… huh?