Type inferencing

In xtlang, type annotations can be attached to the declaration of any variable using a colon, e.g.

  • int_var:i64 (64-bit integer)
  • double_ptr:double* (pointer to a double precision float)
  • closure_ptr:[i64,i32,i32]* (pointer to a closure with two arguments)

Now, most of the examples in this file have been fairly explicit about the types of the variables. Look at the code for xt_add above—in the argument list (a:i64 b:i64) both arguments are identified as i64. What happens, though, if we take out just one of these type annotations?

(bind-func xt_add2
  (lambda (a:i64 b)
    (+ a b)))

;; log shows "Compiled xt_add2 >>> [i64,i64,i64]*"

(xt_add2 2 4) ;; returns 6

Even though we didn’t specify the type of b, everything still compiled fine and the closure returns the correct result. What’s the go with that? Well, it’s because the xtlang compiler in Extempore is a type inferencing compiler. The addition function + in the body of xt_add2 can only add values of the same type. Since the compiler knows the type of a, things will only work out if b is also an i64. And since this guess doesn’t conflict with any other information it has about b (because there isn’t any), then the compiler can infer that the only acceptable type signature for the closure pointer is [i64,i64,i64]*.

How about if we try removing the a type annotation as well?

(bind-func xt_add3
  (lambda (a b)
    (+ a b)))

This time, the compiler prints the message:

Compiler Error: could not resolve ("a" "b" "xt_add3") you could try
forcing the type of one or more of these symbols

There just isn’t enough info to unambiguously determine the types of a and b. They could be both i32, or both floats—the compiler can’t tell. And rather than guess, it throws a compile error.

It’s also worth mentioning that we could have specified the closure’s type directly with the definition of the xt_add3 symbol

(bind-func xt_add4:[i64,i64,i64]*
  (lambda (a b)
    (+ a b)))

(xt_add4 2 9) ;; returns 11

Scheme and xtlang types

Extempore can run both Scheme and xtlang code, but Scheme doesn’t know anything about xtlang’s types—things like tuples, arrays, vectors, closures, and user-defined types through bind-type. Scheme only knows about Scheme types like symbols, integers, reals, strings, c pointers, etc.

There is some (approximate) overlap in these type systems, for ints, floats, strings and c pointers, although even in these cases there are some caveats, e.g. Scheme only supports double precision floats, while Extempore can work with both floats and doubles natively. Similarly, xtlang’s pointers are typed, but Scheme only supports void (opaque) c pointers. Where possible, Extempore will do the work to allow xtlang code from Scheme (coercing argument types), but for any composite types (e.g. list) you can’t call xtlang code from Scheme.

Here’s an example to make things a bit clearer:

;; tuple-maker returns a pointer to a tuple and tuple-taker takes
;; a pointer to a tuple as an argument.

(bind-func tuple-maker
  (lambda ()
    (let ((a:<i64,double>* (alloc)))
          (tset! a 0 42)

(bind-func tuple-taker
  (lambda (a:<i64,double>*)
    (tuple-ref a 0)))

(tuple-maker)               ;; Returns a CPTR (to a tuple, but scheme doesn't know that)
(tref (tuple-maker) 0)      ;; error, scheme doesn't know about xtlang types
(tuple-taker (tuple-maker)) ;; returns 42. scheme can pass *pointers* to tuples around
                            ;; as void pointers, but you lose the type checking

Have a look at examples/core/extempore_lang.xtm for more examples.