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
(fields
(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.