Syntax this and interfaces

Posted on Tue Jun 28, 2016

More work in the ‘typed-languagebranch 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.

Syntax this

Under many programming languages that support some form of object–oriented programming, object–types have methods; in the definition of methods the first argument is the type instance itself. Usually such argument is implicit (like under C++) and it can be accessed through the reserved keyword this.

Until recently, this was not the case under Vicare, where method clauses in record–type definitions required us to explicitly write the first argument:

(define-record-type <duo>
  (fields one two)
  (method (add {O <duo>})
    (+ (.one O) (.two O))))

(define O (new <duo> 1 2))
(.add O)        ⇒ 3

This is no more. (vicare) now exports the fluid syntax this, which is meant to be used to access the implicit first argument of methods. So the code above is now invalid and it must be written:

(define-record-type <duo>
  (fields one two)
  (method (add)
    (+ (.one this) (.two this))))

(define O (new <duo> 1 2))
(.add O)        ⇒ 3

where this references a read–only variable of type <duo>.

Everywhere the clauses method, case-method and method/overload are used to define methods: the first argument is implicit and the fluid syntax this is used in the body of methods to access it.

Interfaces

Interfaces are a mechanism to verify, at expand–time, that: instances of an object–type can be used in a generic expression, because they implement all the needed methods and such methods can be called at run–time through dynamic dispatching. The current implementation is to be considered experimental; some of the internals are in the boot image and the rest is in the library (vicare language-extensions interfaces).

Let’s consider this code:

(define-record-type <a-vector>
  (fields {vec <nevector>})
  (method ({first <top>})
    (vector-ref (.vec this) 0)))

(define-record-type <a-string>
  (fields {vec <nestring>})
  (method ({first <top>})
    (string-ref (.vec this) 0)))

(define-record-type <a-list>
  (fields {vec <nelist>})
  (method ({first <top>})
    (car (.vec this))))

(define (fun O)
  (.first O))

(fun (new <a-vector> '#(1 2 3)))        ⇒ 1
(fun (new <a-string> "ABC"))            ⇒ #\A
(fun (new <a-list> '(a b c)))           ⇒ a

everything works fine in the function fun because all of <a-vector>, <a-string> and <a-list> implement the method ‘first’. The code (.first O) expands into a call to method-call-late-binding, which, at run–time, finds the method implementation functions in the type descriptors of <a-vector>, <a-string> and <a-list>.

Fine, but the code is not type–checked at expand–time. Enter interfaces. Let’s modify the code as follows:

(define-interface <Sequence>
  (method-prototype first
    (lambda () => (<top>))))

(define-record-type <a-vector>
  (implements <Sequence>)
  (fields {vec <nevector>})
  (method ({first <top>})
    (vector-ref (.vec this) 0)))

(define-record-type <a-string>
  (implements <Sequence>)
  (fields {vec <nestring>})
  (method ({first <top>})
    (string-ref (.vec this) 0)))

(define-record-type <a-list>
  (implements <Sequence>)
  (fields {vec <nelist>})
  (method ({first <top>})
    (car (.vec this))))

(define (fun {O <Sequence>})
  (.first O))

(fun (new <a-vector> '#(1 2 3)))        ⇒ 1
(fun (new <a-string> "ABC"))            ⇒ #\A
(fun (new <a-list> '(a b c)))           ⇒ a

everything works almost as before, but the record–type definition clause (implements <Sequence>) causes the expander to validate, at expand-time, that the record–types actually implement a method first with the correct type signature.

Also, the function application (fun ?operand) is validated at expand–time to verify that the type of ?operand is an object–type that implements <Sequence>. Such validation can happen only if the expander is able to determine the type of ?operand; this validation cannot happen at run–time, so, for example, it is impossible for label types to implement interfaces.

Interfaces can implement methods, case–methods and overloaded methods of their own, with the same syntax used for record–types. Right now an interface type cannot declare another interface as super–type; I will decide what to do in future.