The Urn Logo

Latest posts

Version 0.7.0 released

How time flies. It’s been almost three months since the last Urn release, and a whole year since the first one. I can’t say there’s one “super exciting” feature in this update, but instead there’s been many small improvements to the standard library and internals.

Configurable assertions

Often when I write Urn (or any other language for that matter), I find myself needing debug assertions. Namely, assertions which can be enabled at development time or disabled when I need to eck out every last bit of performance. Urn now has built-in support for this, thanks to the demand and desire macros.

Both of these macros take a value which must be upheld and, optionally, an error message which will be displayed if it is not.

> (defun negate (a)
.   (demand (number? a) "a must be a number")
.   (* a -1))

> (negate false)
[ERROR] demand not met: (number? a) (a must be a number).

stack traceback:
  lib/core/demand:29-31: in global 'demand-failure'
  <stdin>:2: in global 'negate'
  <stdin>:1 in main chunk

The difference between the two is when the assertions are enabled. demand is enabled by default, being disabled by passing -flax-checks to the compiler. Conversely, desire is disabled by default, with -fstrict-checks enabling such assertions.

Utilising a similar mechanic, one can also use -fstrict-structs to add typechecks to struct getters and setters. This will ensure the target is a struct of the expected type, preventing you from passing modifying unknown values.

You can enable all “strict” checks with the -fstrict flag, and disable all normal checks with -flax. Note that -flax arguments take precedence over their strict equivalents.

Rewritten native bindings

Interacting with Lua has always been a bit of a tricky thing with Urn. Whilst things have changed several times over the various Urn releases, we’ve never really hit a “sweet spot”. That being said, the latest redesign is substantially closer than we’ve got before.

Previously, one would define a .meta.lua file, which defined how various native definitions worked: whether they were some piece of Lua syntax, or a binding to an arbitrary Lua expression: whether they were some piece of Lua syntax, or a binding to an arbitrary expression. Whilst we are still sticking with this concept, all of these declarations have moved inline with the main define-native file.

This makes things a little cleaner (as we no longer need multiple files) and allows for dynamically generating definitions at compile time.

Compiler cleanup

As the Urn compiler is almost as old as Urn itself, most of it was written without access to some of the more recent features. One of the ways this is most apparent is how raw Lua tables and indexes are used for every structure, meaning you have no clue what (.> x :name) is referring to. Additionally, it makes it awfully easy to misspell field names, or set the correct field on entirely the wrong object.

This Urn release makes great strides in “sturdying-up” the compiler, converting many of the data structures to use defstruct. Not only does this provide better type safety, it also makes it much easier to read and write documentation on specific fields.

REPL reloading

One of the workflows I often rely on is loading files into the REPL, checking functions work as expected, and then modifying the files if not. Sadly, each time you change the file, you need to restart the REPL, meaning you have to start from scratch. This release adds the :reload (or :r) command to the REPL. This will scan all loaded modules, determine which ones have changed, recompile them. This means you can add or remove features without ever having to leave the comfort of Urn.

We’ve also rewritten the online REPL, using lua.vm.js to run the Urn compiler in the browser. This should make things more responsive (and removes the need to rely on friends’ servers). Many thanks to RawGit for their GitHub CDN service, which is used to fetch the standard library.

Do note that this is just a snapshot of the changes included in this release of Urn. Do check the full changelog or peruse the docs for more information.

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)
                (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:

  (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
        if empty_3f_1({tag="list", n=0}) then
                return 1
                return 2

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
                foo = 2
        return foo

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.

Version 0.6.0 released

Wow, it’s been a while since the last update. During that time, we’ve been doing a lot of work on the standard library. So let’s dig right in an see what’s changed.

Standard library restructuring

Urn’s standard library has always been a bit of a mess, with features scattered everywhere. Whilst some chaos is inevitable due to having to bootstrap the library, there was still lots of improvement. Sadly, deciding on a good approach proved more complicated than expected.

For Urn 0.6 we finally went about this restructuring, splitting Urn into several modules such as core (for bootstrapping the stdlib), data (data manipulation libraries) and test (used for writing tests). It’s worth noting that some programs may need changing to work with the new layout, but rest assured we won’t be changing it again any time soon.

Extended math library

Lisp has always had a history of numerical computing, and Urn does not disappoint. 0.6 adds support for rationals, complex numbers, vectors and matrices. It’s now that much simpler to do your maths homework in Urn!

In order for these features to work fluidly together, we’ve added a collection of generic arithmetic methods, such as n+ and n* which operate on arbitrary data structures. These methods, along with rational literals, bring a lot to the table.

> (import math/vector ())
> (n* (vector 1 2 3) 2/3)
out = [2/3 4/3 2/1]

String formatting changes

hydraz has done a lot of work on developing more powerful ways to format strings. Whilst string/format is useful, it can still be tedious to use. Thankfully the data/format library accepts format strings which neatly integrate with pretty printing, as well as allowing keyword arguments and interpolation of symbols in the current scope. I’d really recommend checking out the documentation for more information.

> (format nil "0x{foo%x}" :foo 123)
out = "0x7b"

Internal changes

Whilst the obvious changes have occurred within the libraries, the compiler’s internals have also changed. Aside from improved codegen, line mappings are now substantially more accurate. Consequently code coverage and error messages are more informative - always a perk when debugging!

Version 0.5.1 released

We’ve just released another Urn update - 0.5.1. This is a relatively small update, mostly composed of bug fixes and performance improvements. That’s not to say there aren’t some useful new features though!

REPL improvements

The Urn REPL is a great tool for testing and prototyping code. It’s something we regularly use, and so are always eager to improve it.

One task we often find ourselves doing is loading up the REPL, importing a module and testing a small feature in it. This process has now been streamlined: when using the --repl flag, any file provided on the command line will be automatically imported into the current scope.

We’ve also added a :view command, which allows you to preview a symbol’s definition. This saves you switching between source code and the REPL, trying to remember how you got that awesome feature to work.

Shows the result of :view command

Optimised optimiser

The Urn compiler has never been the fastest beast, with the optimiser being the worst offender. Whilst the optimiser is great at reducing the size (and speed) of generated code, it also takes considerable time to run. Even worse, much of the optimiser’s time was just spent in iterating over every node - not even running the various passes! Something had to be done.

Urn 0.5.1 introduces a new framework for optimisations, doing something so obvious I’m surprised we weren’t doing it from the start. Instead of running each pass sequentially, we have one unified visitor object which will call each pass for every node it hits. Not only does this substantially reduce the visitor overhead, it reduces the number of times we have to run a pass, resulting it in an even bigger performance increase! We’ve also made some improvements to definition and usage tracking, which ensures that the information is more up-to-date, and so the optimiser can make smarter decisions.

These various improvements have resulted in a 2-3x performance increase in the optimiser (depending on Lua version and implementation). In fact, it now takes less time to optimise the compiler than it does to load it. There is evidently room for performance improvements elsewhere!

Version 0.5.0 released

Updates my dears, and not a moment too soon. It’s been 100 commits since our last release, and each commit is packed with lispy goodness. OK, maybe not every commit. In this time, we’ve reached 1000 commits which, whilst a rather arbitrary milestone, it still a milestone we can celebrate. There’s been more changes than I can cover here, so I’d really recommend checking out the release nodes for the whole picture. Anyway, here’s a small summary of what’s new:

Multimethods (aka defgeneric)

Whilst Urn prides itself on the extensible power that macros offer, the extensibility of the standard library has historically been lacking. There are several functions which do a long if-elseif chain of type comparisons, with a final metatable lookup if all else fails. This is obviously undesirable and so something needed to be done. Enter multimethods.

If you’ve used an object oriented language such as C++ or Java then you will have used methods before, and so multimethods will not be an entirely alien concept. However, instead of dispatching based on the type of the first argument, it uses all arguments to determine which function to call. This makes multimethods a much more powerful solution.

In order to demonstrate their power, we’ll create a generic merge method. This’ll attempt to merge two objects together and return the resulting object. First we define a new method with defgeneric, providing our argument names and a docstring:

(defgeneric merge (x y)
  "Merge X and Y together, returning a new object.")

We can now call merge, but we’ll just get an error. After all, we haven’t defined any implementations yet. This is done with defmethod, specifying the argument types and an implementation. Let’s do a couple of simple ones:

(defmethod (merge string string) (x y) (.. x y))
(defmethod (merge list list) (x y) (append x y))

Now let’s use these:

> (merge "foo" "bar")
out = "foobar"
> (merge '(1 2 3) '(4 5 6))
out = (1 2 3 4 5 6)

Of course, if you’ve got a fancy structure which can be merged, its trivial to add support for that too. We’ve made great use of this feature in the standard library, converting pretty and eq? to use it.

I’d really recommend reading hydraz’s blog for more information about this feature. He’s the one who wrote it, and goes into much more detail about the implementation and rational behind it. Now’s when I’d normally insert a cynical comment about no-one getting this far, but it’s late and there are plenty more features to cover…

Fancy structures

One of the gems of Lua is it’s table. This simple data structure forms the base of many of Urn’s standard types. However, using passing around tables and indexing them via strings does rather take the idea of abstraction and smash it on the floor. We’ve written the urn/struct library to provide a more intuitive way to create your own data structures.

This library provides the defstruct macro. This provides a small DSL which allows you to declare fields, constructors and more. This will, in turn, generate the appropriate constructor, getters, setters and pattern matching utilities. Sounds great? Well let’s jump in and declare the struct staple - a point.

(defstruct point
    (immutable x)
    (immutable y)))

(defmethod (pretty point) (p)
  (string/format "(x=%d y=%d)" (point-x p) (point-y p)))

This structure definition will create a constructor (make-point) and a type predicate (point?). Each field definition will specify an argument to the constructor and a getter. If the field is marked as mutable, then a setter will also be generated. We also chuck in a pretty implementation for good measure.

Now we can start constructing points, indexing their fields and printing them:

> (point 1 2)
out = (x=1 y=2)
> (point-x (make-point 1 2))
out = 1
> (point-y (make-point 1 2))
out = 2

If you don’t like these function names, then customising them is trivial. One can also make particular fields hidden package-local, if you have implementation specific fields. See the documentation for more information.

Testing, coverage and stability

All these new features a awfully nice, if we don’t have any guarantees they actually work correctly. That’s why we’ve spent a lot of time getting our testing infrastructure up to date. I won’t go into the details as frankly, it isn’t that interesting but here’s a couple of highlights:

Example testing

The Urn documentation is full of example usage for various functions, complete with inputs and outputs. These examples are perfect for a trivial test case, to ensure the basic functionality is there. To make best use of these examples, we’ve written a compiler plugin which extracts codeblocks from docstrings and runs them, verifying the output is as expected.

Whilst I’m not proposing doing away with normal tests - after all, you don’t want to include every edge case in an example - they are a small step in making sure Urn is fully tested. Furthermore, it allows us to ensure all examples are up to date, and provides an incentive to write more documentation - always a plus.

Code coverage

Tests are great for seeing what works, but it’s much harder to find what doesn’t work. One way to help with this is to determine what the test suite doesn’t check. And so, of course, we’ve written a small code coverage utility for Urn. It generates files in the same format as LuaCov, and so provides some interpobility with other Lua-based tools. To get started, just run your code with the --profile=coverage switch.

Codegen and optimiser improvements

Normally I’d bang on about the most exciting optimisation changes this release. However, for better or for worse, there haven’t been many major changes. Instead, there’s been a lot of work on stabilising the various systems, resulting in a less buggy and more consistent experience. Whilst it’s far from perfect, we’re one step closer to not blowing up in your face the whole time.