Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Lisp

Theme-D - Extending Scheme with Static Typing

0.00/5 (No votes)
23 Sep 2024CPOL6 min read 2K  
The Theme-D programming language extends Scheme with static typing
Theme-D contains a static type system, object system, and module system. The object system resembles Guile object system (GOOPS). Theme-D also supports generic procedures and parametrized procedures, types and classes. You can write Theme-D GUI programs using the GTK library with software package Theme-D-Intr.

Objects, Types, and Classes

All values of Theme-D variables are objects. The runtime type of each object is a class. Builtin classes are <object>, <class>, <integer>, <real>, <boolean>, <null>, <symbol>, <string>, <character>, and <keyword>. A user-defined class declares the fields that instances of this class (i.e. objects whose runtime class is this class) contains. Classes <object> and <class> are inheritable but the other builtin classes are not. Each class has a direct superclass. All classes inherit from <object> either directly or indirectly. A class may be declared immutable, which means that the values of the fields of an instance of the class can't be changed after the object has been created. If a class is declared equal-by-value the equality of instances of the class is computed by checking the equality of the fields of the instances. If a class is not equal-by-value the instances of this class are equal if they are the same object. A class may declare a zero value. This feature is used mainly for numerical classes.

Here is an example class for rational numbers:

(define-class <rational>
  (attributes immutable equal-by-value)
  (inheritance-access hidden)
  (fields
    (i-numer <integer> public hidden)
    (i-denom <integer> public hidden))
  (zero (make <rational> 0 1)))

Type is a more general concept than class. There are two kinds of types: classes and unions. An object is an instance of an union if the runtime type of the object inherits from some component type of the union. The declared type of a variable is called its static type. The runtime type of a variable is always a subtype of its static type. Here is an example of a union type implementing a list of integers:

(declare <my-list> :union)

(define <my-list>
  (:union (:pair <integer> <my-list>) <null>))

Theme-D generates a constructor for each nonabstract class. The constructor is a procedure generating the instances of the class. The arguments of the constructor are declared in a construct clause of the class. These arguments may be used to compute the initial values of the fields of the class instances. The accessibility of the constructor may be declared in a constructor-access clause. If the access is public the constructor may be invoked anywhere in the code. If the access is module the constructor may be invoked only in the module where the class is declared. If the access is hidden the class is abstract and no direct instances of the class can be constructed. Here is an example of a class defining a constructor:

(define-class <rectangle>
  (construct ((_x1 <integer>) (_y1 <integer>)
              (_x2 <integer>) (_y2 <integer>)))
  (fields
    (x1 <integer> public module _x1)
    (y1 <integer> public module _y1)
    (x2 <integer> public module _x2)
    (y2 <integer> public module _y2)
    (width <integer> public module (+ (- _x2 _x1) 1))
    (height <integer> public module (+ (- _y2 _y1) 1))))

Constants

Theme-D has two kinds of variables: constants and mutable variables. The value of a constant cannot be changed after it is created.

Programs and Modules

All the code in Theme-D is organized into units. A unit is either a program, an interface or a body. A program is either a proper program or a script. A combination of an interface and the body that implements it is called a module.

An interface contains all the definitions or declarations for the variables that the module exports. An interface contains only declarations for the procedures and the parametrized procedures that the module exports. A body contains definitions of all private variables of the module and definitions of all the procedures and the parametrized procedures declared in the interface. Both the interface and the body may import other modules using keyword importi. An interface may reexport variables imported from other modules. An interface may also export another module using keyword import-and-reexport

We define a module implementing rational numbers as an example. Here is the interface:

(define-interface rational

  (import (standard-library core))

  (define-class <rational>
    (fields
      (numerator <integer> public module)
      (denumerator <integer> public module)))

  (declare rational+ (:simple-proc
    (<rational> <rational>) <rational> pure))
                  
  (declare rational- (:simple-proc
    (<rational> <rational>) <rational> pure)))

and here is the body:

(define-body rational

(define-simple-proc rational+
    (((rat1 <rational>) (rat2 <rational>))
     <rational> pure)
  (make <rational>
    (+ (* (field-ref rat1 'numerator) (field-ref rat2 'denominator))
       (* (field-ref rat1 'denominator) (field-ref rat2 'numerator)))
    (* (field-ref rat1 'denominator) (field-ref rat2 'denominator))))

(define-simple-proc rational-
    (((rat1 <rational>) (rat2 <rational>))
     <rational> pure)
  (make <rational>
    (- (* (field-ref rat1 'numerator) (field-ref rat2 'denominator))
       (* (field-ref rat1 'denominator) (field-ref rat2 'numerator)))
    (* (field-ref rat1 'denominator) (field-ref rat2 'denominator)))))

Generic Procedures

A generic procedure is a collection of simple or parametrized procedures, which are called methods. A generic procedure is either normal or virtual. Normal generic procedures are lexically scoped and virtual generic procedures dynamically scoped. When a generic procedure is called and an argument list is passed to it Theme-D first checks which of the methods of the generic procedure can be called with the argument list, i.e. the type af the argument list is a subtype of the method argument list type. Theme-D then finds out which of the suitable methods is the best match. If a unique best match is not found an exception is raised. These checks occur compile-time for the normal generic procedures and run-time for virtual generic procedures. In some cases virtual generic procedure dispatch may be optimized with a static dispatch.

Suppose that a virtual generic procedure has two distinct methods having argument list types A and B and result types R and S , respectively. If A is a subtype of B then R has to be a subtype of S . This is called the covariant typing rule. The covariant typing rule allows Theme-D to deduce a supertype of the result type of a virtual generic procedure application at compile time.

Consider the following classes:

(define-class <object-with-id>
  (fields
    (str-id <string> public module)))

(define-class <widget>
  (superclass <object-with-id>)
  (fields
    (i-x <integer> public module)
    (i-y <integer> public module)))

and the following code:

(define-simple-proc myproc (((x <object-with-id>))
                            <none> nonpure)
  (my-print x)
  (console-newline))

(define-main-proc (() <none> nonpure)
  (let ((x1 (make <widget> 10 20)))
    (myproc x1)))

If we define my-print as a normal generic procedure:

(define-simple-method my-print
    (((x <object-with-id>)) <none> nonpure)
  (console-display-line (field-ref x 'str-id)))

(define-simple-method my-print
    (((x <widget>)) <none> nonpure)
  (console-display-line (field-ref x 'str-id))
  (console-display-line (field-ref x 'i-x))
  (console-display-line (field-ref x 'i-y)))

the call of generic procedure my-print invokes the first method.

If we define my-print as a virtual generic procedure:

(define-simple-virtual-method my-print
    (((x <object-with-id>)) <none> nonpure)
  (console-display-line (field-ref x 'str-id)))

(define-simple-virtual-method my-print
    (((x <widget>)) <none> nonpure)
  (console-display-line (field-ref x 'str-id))
  (console-display-line (field-ref x 'i-x))
  (console-display-line (field-ref x 'i-y)))

the call of generic procedure my-print invokes the second method.

Parametrized Classes, Types, and Procedures

Theme-D allows declarations of classes, types, and procedures having type parameters. Here is an example of a complex number class taking the number type of the real and imaginary parts as a parameter:

(define-param-class :my-complex
  (parameters %number)
  (attributes immutable equal-by-value)
  (inheritance-access hidden)
  (zero (make (:my-complex %number) (zero %number) (zero %number)))
  (fields
    (re %number public hidden)
    (im %number public hidden)))

Here is a list type taking the component type as a parameter:

(define-param-logical-type :my-list (%type)
  (:union (:pair %type (:my-list %type)) <null>))

Note that Theme-D contains :uniform-list as a built-in type.

Here is a procedure (method) iterating over a list and taking the argument list and result list component types as parameters:

(define-param-method map1
    (%argtype %result-type)
    (((proc (:procedure (%argtype) %result-type pure))
      (lst (:uniform-list %argtype)))
     (:uniform-list %result-type)
     pure (optimize-proc-arg proc))
  (match-type-strong lst
    ((<null>) null)
    ((lst1 (:nonempty-uniform-list %argtype))
     (cons (proc (car lst1))
           (map1 proc (cdr lst1))))))

Exceptions

Theme-D support exceptions for handling erroneous and exceptional conditions. Here is an example:

(define-main-proc (((l-args (:uniform-list <string>)))
    <none> nonpure)
  (guard-without-result
    (exc
      ((rte-exception? exc)
       (case (get-rte-exception-kind (cast <condition> exc))
         ((uniform-list-ref:index-out-of-range)
          (console-display-line "Not enough arguments"))
         ((io-error)
          (console-display-line "Error opening file"))
         (else (raise exc))))
      (else (raise exc)))
    (let* ((str-filename (uniform-list-ref l-args 1))
           (fl (open-input-file str-filename))
           (str-contents (read-string fl)))
      (close-input-port fl)
      (console-display-line str-contents)
      (console-display-line "Success."))))

Signatures

Signatures are an experimental feature of Theme-D. They resemble Java signatures but Theme-D signatures are multiply dispatched.

Here is a definition of signature <stack> and class <a> implementing the signature:

(define-virtual-gen-proc push)
(define-virtual-gen-proc pop)

(define-signature <stack> ()
  (push (this <integer>) <none> nonpure)
  (pop (this) <integer> nonpure))

(define-class <a>
  (fields
    (l-contents (:uniform-list <integer>) public module null)))

(define-simple-virtual-method push
    (((store <a>) (item <integer>)) <none> nonpure)
  (field-set! store 'l-contents
              (cons item (field-ref store 'l-contents))))

(define-simple-virtual-method pop
    (((store <a>)) <integer> nonpure)
  (let* ((l1 (cast (:nonempty-uniform-list <integer>)
                   (field-ref store 'l-contents)))
         (result (car l1)))
    (field-set! store 'l-contents (cdr l1))
    result))

Theme-D supports parametrized signatures, too. Here is the previous example modified so that the stack element type is taken as a parameter:

(define-virtual-gen-proc push)
(define-virtual-gen-proc pop)

(define-param-signature :stack (%element) ()
  (push (this %element) <none> nonpure)
  (pop (this) %element nonpure))

(define-param-class :a
  (parameters %element)
  (fields
    (l-contents (:uniform-list %element) public module null)))

(define-param-virtual-method push (%element)
    (((store (:a %element)) (item %element)) <none> nonpure)
  (field-set! store 'l-contents
              (cons item (field-ref store 'l-contents))))

(define-param-virtual-method pop (%element)
    (((store (:a %element))) %element nonpure)
  (let* ((l1 (cast (:nonempty-uniform-list %element)
                   (field-ref store 'l-contents)))
         (result (car l1)))
    (field-set! store 'l-contents (cdr l1))
    result))

Numerical Tower

Theme-D supports numerical tower resembling the numerical tower in Scheme. The tower consists of the following classes:

  • <integer>
  • <rational>
  • <real>
  • <complex>

Classes <integer> and <real> are built-in to the language and classes <rational> and <complex> are implemented by the standard library.

Every integer can be converted to a rational number so that these numbers are equal by predicate =. Similarly, every real number can be converted to a complex number and the numbers are equal by =. Every rational number can be converted to a real number but the rational and real numbers may not be equal by =.

GObject Introspection Support

Software package Theme-D-Intr makes it possible to use libraries supporting the GObject introspection features in Theme-D programs. In particular, it is possible to write GUI programs using the GTK library. Every program using Theme-D-Intr must define an introspection file, where the imported classes (and functions that are not methods) are declared. It is also possible to override the method definitions in the introspection file.

Here is the "Hello world" program written using Theme-D-Intr:

(define-proper-program hello

  (import (standard-library core)
          (standard-library list-utilities)
          _intr-imports)

  (define-simple-method activate-my-app
      (((args (rest <object>))) <object> nonpure)
    (let* ((app (cast <gtk-application> (uniform-list-ref args 0)))
           (window (cast <gtk-window> (gtk-application-window-new app)))
           (button (cast <gtk-button>
                         (gtk-button-new-with-label "Hello, World!"))))
      (gtk-widget-set-width-request window 400)
      (gtk-widget-set-height-request window 300)
      (gtk-window-set-child window button)
      (gtype-instance-signal-connect
       button 'clicked
       (lambda (((args2 (rest <object>))) <object> nonpure)
         (g-application-quit app)
         null))
      (gtk-window-present window)
      (gtk-widget-show button)
      #f))
    
  (define-main-proc (() <none> nonpure)
    (let* ((str-app-name "org.tohoyn.hello")
           (app (gtk-application-new str-app-name null)))
      (gtype-instance-signal-connect
       app 'activate activate-my-app)
      (let ((status (g-application-run app null)))
        status))))

and here is the corresponding introspection file:

(intr-entities
  (version Gtk "4.0")
  (classes
    (Gtk Widget)
    (Gtk Application)
    (Gtk ApplicationWindow)
    (Gtk Window)
    (Gtk Button)))

Points of Interest

Bootstrapped Environment

Theme-D package contains bootstrapped compiler and linker. For some reason these are slower than the corresponding implementations in Scheme.

Optimization of Type Matching

See the procedure map1 in this section. We know that the type of lst is a subtype of (:union <null> (:nonempty-uniform-list %argtype)). Consequently we only need to check if lst is null in the match-type-strong expression.

More Information

The Theme-D homepage is located here and the Theme-D-Intr homepage here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)