The Urn Logo

Version 0.6.1 released

Normally I start these update posts with some comment about how much has changed, or how long it’s been since the last one. Sadly, I can’t think of anything to say so let’s just jump right into some key changes:

Method improvements

Methods are a really powerful way of writing functions which can handle all sorts of data types. That being said, they are not without their problems and so we’re always working to improve them. One issue is the large increase in code size that using methods results in. In Urn 0.6.1, we’ve tried to lower that overhead, resulting in even smaller files!

Alongside with that, we’ve also added the ability to specify wrappers within methods. Every call to your method will go through this delegate, allowing one to provide specialist logic. This is used by the eq? method to check using the == operator before trying anything else.

(defgeneric eq? (x y)
  "Compare values for equality deeply."
  :delegate (lambda (x y)
              (if (= x y)
                true
                (myself x y))))

Code generation and optimisation tweaks

Interestingly enough, method delegates exposed several places where code generation could be further improved. One such improvement was forcing bindings to be generated as local definitions rather than directly called functions. This makes code more readable and potentially allows further improvements as statements can be emitted more sensibly. For instance, consider the following code:

(print!
  (with (foo (if (empty? '()) 1 2))
    (+ 1 foo)))

here’s how it would be compiled on 0.6.0:

return print1(1 + (function(foo)
        return foo
end)((function()
        if empty_3f_1({tag="list", n=0}) then
                return 1
        else
                return 2
        end
end)()))

and here’s the equivalent code on 0.6.1:

return print1(1 + (function()
        local foo
        if empty_3f_1({tag="list", n=0}) then
                foo = 1
        else
                foo = 2
        end
        return foo
end)())

Another interesting optimisation we now apply is lowering “deferrable” variables. For instance, consider this definition:

(with (foo '())
  (when (empty? '())
    (debug foo)))

The definition of foo is only needed if then when block is executed, and so we’re able to “push” the definition down:

(when (empty? '())
  (with (foo '())
    (debug foo)))

Whilst this code doesn’t appear much in practice (or at least in this overly simplistic form), it’s nice to catch the few occasions where it does.