Latest posts
Version 0.2.1 released
It’s friday, it’s five to five and it’s Crackerjack Urn update time. Well, only one of those are true but you
should still be excited. There are a couple of exciting things in this update, so let’s get cracking.
Top-level unquote
If you’ve been following the tutorials you’ll recall
that syntax-quote
and unquote
allow you to step up and down the “compilation ladder”, syntax-quote
moving from
code to data and unquote
moving back up to code. With this update, you can unquote
even further up the ladder,
allowing you to execute arbitrary code at compile time without the use of macros.
For example, say you want to have a list of the first 10 square numbers. Previously, you could define it as
(define squares (map (cut ^ <> 2) (range 1 10)))
However, this means it’ll be executed every time you run your program. This could slow it down. Instead, let’s generate these at compile time:
(define squares ,(cons `list (map (cut ^ <> 2) (range 1 10))))
This compiles to
(define squares (list 1 4 9 16 25 36 49 64 81 100))
Of course, there are far more powerful applications of this which we won’t go into here.
Property checking
Property checking is a form of testing where you can write some form of assertion and it will generate random inputs, trying to break it. For instance:
(check [(list x)]
(eq? x (reverse (reverse x))))
Will check if reversing a list twice always results in the same list. This means you can write general assertions for all inputs, rather than specific test cases. For more information, see the documentation.
Lambda-binding syntax
let
and friends (let*
, letrec
, etc…) now allow creating functions just by specifying the arguments and function
body - no lambda required!
(letrec [(factorial (x accum)
(if (= x 0)
accum
(factorial (- x 1) (* accum x))))]
(print! (factorial 3 1)))
Version 0.2 released
It’s sometime after 11o’clock (well, it is in my timezone) which means it must be time for an Urn update!
Line mappings
We’ve finally got round to adding line mapping, which means any code executed whilst using the Urn compiler will have its positions mapped back to the original source. For instance, consider the following code:
(defun foo (x y)
(+ x y))
(defun do-bar! (x y)
(succ (foo (succ y))))
(print! (do-bar! nil 23))
When executed (lua run.lua demo/fail.lisp --run
), you’ll get something like:
demo/fail.lisp:2 (out.lua:24): attempt to perform arithmetic on a nil value (local 'y')
stack traceback:
demo/fail.lisp:2 (out.lua:24): in upvalue 'foo'
demo/fail.lisp:5 (out.lua:27): in local 'do-bar!'
demo/fail.lisp:7 (out.lua:29): in main chunk
As you can see, all line numbers are converted back to their positions in the root lisp file. In addition to that, names
are un-mangles, meaning you get do-bar!
rather than doBar_21_1
. It is worth noting that this metadata is not
persisted, so running the compiled code directly will not result in mapped lines.
Benchmarking
We’ve also added a fancy benchmarking and timing library, meaning you can wrap function definitions with (time! ...)
in order to time every invocation. For instance:
(time! (defun foo (bar baz)
(with (sum 0)
(for i bar baz (set! sum (+ sum i))))))
Also in this release:
- Add
for-pairs
macro for easier iteration over structs. - Remove
pair
library.
Version 0.1.4 released
Well, another release with a load more improvements! The highlights:
- Add support for module level documentation.
- Further improvements to how variables are inlined, meaning even more constant expressions get folded.
- Pattern matching! Ahahahr. Hype! Hype!
Right, if you’ve never seen pattern matching before, then you can think of it as a switch statement on steroids. In short, it allows you to compare the specified object against a series of “patterns”: these can be constants, descriptions of a list, or arbitrary functions. For instance, the pattern:
(case x
[((?x ?y) . ?xs) (print! "List where the head has two entries" x y (pretty xs))]
[(?x . ?xs) (print! "List with head and tail" (pretty x) (pretty xs))]
[() (print! "Empty list")])
- If you run it against a list looking something like
((1 2) 3 4 5)
then the first pattern will match, withx = 1
,y = 2
,xs = (3 4 5)
as it has the same “layout”. - If it is a list with at least one entry (such as (1 2 3)) then the second pattern matches, giving
x = 1
,xs = (2 3)
. - If the above pattern didn’t work, then we must have an empty list, meaning the third pattern matches.
As you can see, this is much shorter than the equivalent Lua (or Lisp without match) code. For the full capabilities of the pattern matching library, see the documentation.
New documentation site
One of the common pieces of feedback we’ve had is that people don’t really know what they’re doing with Urn. Over the last couple of days I’ve been working on a series of tutorials for Urn: explaining some of the language features, introducing macros, and what not. Hopefully this make getting started with Urn a breeze.
However, that being said, getting documentation right is hard - especially if you are overly familiar with the language already. If you do struggle to understand something it means the docs are failing! Drop me a line (through the forums, GitHub, GitLab, Discord, Gitter, etc…) and I’d be happy to clarify things and fix the docs.
Many thanks to all those people who have given feedback on Urn so far. Go forth, and try out the docs!
Version 0.1.3 released
I’ve just pushed another release of Urn.
Firstly, good news for Windows users. Urn will attempt to work out whether your terminal supports ANSI colours, only using them if so. If your terminal is incorrectly detected, please create an issue on GitHub or GitLab. I’ve also worked in improving the optimiser. It now will correctly simplify variable accesses to their root value. Something like:
(define x <compile_expr>)
(define y x)
(define z y)
(print! z)
will simplify to
(define x <compile_expr>)
(print! x)
You can see the result of this optimisation on files like this.