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?