An Introduction to Urn
Hello and welcome. These tutorials aim to guide you through Urn, a Lisp dialect which compiles to Lua. We’ll assume you have some prior programming knowledge. A little Lua knowledge is useful in places, though not required.
Lisp is a whole family of programming languages, of which Urn is just one member. Other members include Common Lisp, Scheme and Clojure. All dialects have their own distinct features and capabilities, but they all have one thing in common: the use of lists to represent data and code. This makes metaprogramming, the ability to modify and extend the program, incredibly easy.
First off, you’ll want to install Urn. The easiest way to do this is clone the repository using Git:
> git clone https://gitlab.com/urn/urn.git && cd urn
You can now launch the Urn REPL: this allows you to enter code and execute it, allowing for immediate feedback.
> lua bin/urn.lua
You should have an interactive prompt, where you can enter simple expressions like numbers (
123) and strings
"hello"). Let’s do the classic “Hello, world!” program:
> (print! "Hello, world!")
So let’s dissect what’s happening here. First off, we have the parenthesis (
( ... )): this is a list. Anything inside
these parentheses are the elements on the list: in this case
"Hello, world!". Generally a list represents
a function call: the first element of the list is invoked with the remaining elements as arguments.
In this case, our function is
print!. This is a symbol: a reference to a variable defined somewhere else. This
print!, is a function which prints its arguments to the terminal. It’s worth noting that symbols can
contain almost any character.
A word on naming: The
!character doesn’t have any semantic meaning here. By convention, any method which has a side effect (such as printing to the terminal, or modifying a value) we put a
!at the end. Similarly, functions which return a boolean often end with
The next thing to do, as in any programming language tutorial, is try some very basic arithmetic. Unlike many languages, this is done like any other function call:
> (+ 2 3) 5
In fact, you can just type
+ in the REPL to see that it is just a function. This means you can pass it around as an
You can, of course, have lists inside lists (and lists inside lists inside lists, etc…).
> (+ 2 (* 4 5)) 22
Defining our own functions
Of course, it’s no fun if we only use built in functions, so let’s define our own. This is done using the
macro. This looks like an ordinary list, but instead of invoking a function, it creates a function with the specified
name, arguments and body.
> (defun times-two (x) (* x 2))
Again, let’s break this down. Whilst we’re not calling a function here, the premise is the same. The first argument,
times-two, is the name of the function we’re defining. Next comes a list of arguments this function takes. Do note,
unlike before, this argument list does not represent a function call - it has a special meaning here. All remaining
defun’s list are expressions which are executed when the function is called. The last expression will be
returned by default.
So, now we’ve defined this function, let’s use it:
> (times-two 2) 4 > (times-two 3) 6
More on naming: Some languages handle multiple word variable names by using
camelCase. Lisp uses hyphens instead to separate words, it just looks a little prettier.
Of course, it would be awful if you had to enter your code into the REPL every time you wanted to execute it. Instead,
you can save your program to a
.lisp file and execute it. Open up your favourite text editor (or
Vim Emacs if you
haven’t got one) and create a
times-two.lisp file. Let’s define our
times-two function again, and print out the
double of a couple of numbers.
(defun times-two (x) (* x 2)) (print! (times-two 2)) (print! (times-two 3))
A word on formatting: Urn, and lisps in general, have a rather unusual approach to formatting. You generally indent with two spaces, but you also try to line up expressions on the same “level” with each other. For example:
(defun times-two (x y) (* x 2))
You should also note where the parentheses go here: instead of putting each trailing parenthesis on a new line like you might with a C style language, everything goes on the last line of the block.
Now that you’ve saved your
times-two.lisp file, you can compile it to Lua and execute it.
# This will generate a file called "out.lua" > lua bin/urn.lua times-two.lisp # Which can then be run as normal > lua out.lua
If you pass
--run when compiling, it will do both these steps at once:
> lua bin/urn.lua times-two.lisp --run
You can also use
--output to specify a different file to write to:
> lua bin/urn.lua times-two.lisp --output my-times-two.lua