An Overview of MoonScript
MoonScript is a language that compiles to Lua inspired by CoffeeScript. The MoonScript reference manual is a great place to learn exactly how MoonScript works with Lua, but if you don’t know Lua it can be difficult to read.
As an alternative, here is a tutorial and overview of some of MoonScript’s syntax with no Lua knowlege required.
If you've worked with JavaScript (or CoffeeScript) before, you'll feel right at home with the features provided by MoonScript (and Lua).
Hello World
Writing “Hello World” is as simple as calling the print
function with some
value, like the string "Hello world"
:
-- I'm a comment!
print "Hello world"
Comments begin with --
and continue to the end of the line.
print
is a built in function takes any number of arguments and will print
each one separated by a tab character.
You can read more about print
on the Lua reference
manual. MoonScript is
just another way of writing Lua, so all built in Lua functions can be used
directly in MoonScript as well.
Variables
Variables come into existence when they are first assigned, and they are overwritten when assigned again:
x = "Hello"
y = 4000
y = 34
If you are creating or assigning multiple variables at once, you can list their
names on the left of the =
and their values on the right.
a, b, c = 1, 2, 3
Values
The basic types in MoonScript numbers, strings, booleans,
functions, tables and nil
. Numbers and strings are
just as you expect. We can join strings using the concatenation operator, ..
,
and we can get the length using the length operator, #
.
x, y = 10, 22
x += (y - 100) / 2
a, b = "hello", "world"
print a .. " " .. world
print "length of a:", #a
The two boolean values are true
and false
. The not
operator is used to
negate a boolean value. and
and or
keywords perform their respective
boolean operation.
javascript = true
moonscript = not javascript
fast = true
strong = false
warrior = tall and strong
false
and nil
are considered falsy and everything else is truthy. For
example the result of the expression not "hello world"
is false
because
that is the opposite of a truthy value.
We can compare values with all the standard comparison operators: ==
, !=
,
<
, <=
, etc. Greater than and less than are only defined for numbers (by
default).
The other types are discussed below.
Functions
Functions are first class objects, meaning they are values that are stored in variables just like any other object (like numbers or strings).
Functions are created using the arrow operator. A blank function that takes no
arguments and does nothing is simply the arrow ->
. You can call a function
with no arguments by using the !
suffix operator.
-- here we create, then call a function that does absolutely nothing!
fn = ->
fn!
If the body of the function has only one statement, it can be placed after the arrow on the same line. If a function is made up of multiple statements they can be placed new indented lines following the arrow.
say_hello = -> print "Hello there"
say_hello!
say_hello!
say_more = ->
print "My favorite snack is:"
print "Ice Cream"
say_more!
Arguments
If a function takes arguments, we can specify a list of argument names wrapped in parentheses before the arrow:
say_name = (name) -> print "Your name is:", name
say_name "MoonScript"
A function may take any number of arguments when called. If there
are too many, the extra ones are discarded, if there are too few, the missing
arguments get a value of nil
.
When calling a function that has arguments, argument values are listed after the function name separated by commas. Parentheses are optional (but required in some cases to avoid ambiguity).
print_sum = (x, y) -> print x + y
-- these two are exactly the same
print_sum 10, 12
print_sum(10, 12)
Notice now that print
is just a regular function, and we've been calling it
like this all along. In fact, we can give it another name, or remove access to
it altogether.
write = print
write "hello"
print = nil
print "hi" -- this is now a runtime error
Returning
Functions in MoonScript implicitly return the last statement. This means that
the last statement in the functions body is the return value. You can also
explicitly return by using the return
keyword.
-- these two do the same thing:
add_1 = (x, y) -> x + y
add_2 = (x, y) -> return x + y
If MoonScript can’t figure out how to get a return value from the last
statement, nil
is returned. Note that assigning a variable does not have a
return value, so an assignment at the end of a function body will cause the
function to return nil
.
Default Argument Values
Function arguments can have default values. Just assign the argument name a
value in the argument list using =
.
add = (x, y, is_verbose=true) ->
print "adding x and y" if is_verbose
x + y
The default value is used whenever nil
is passed to that argument, which
happens when fewer number of values than arguments are passed into the
function.
Multiple Return Values
A function may return multiple values. Just separate them by commas:
double_both = (a, b) -> a * 2, b * 2
x, y = double_both 10, 20
If you are calling a function and it’s last argument is another function call that returns multiple values then all of those return values are passed as arguments.
If the function with multiple return values is not called in the last argument position then only the first value returned is kept and the rest are discarded.
some_numbers = -> 1,2,3
print some_numbers! -- will print 1 2 3
print some_numbers!, "hello" -- only prints 1, "hello"
Variable Number of Arguments
If your function takes multiple arguments, then we can join them all into a
special argument name called ...
. It must be at the end of the argument name
list in a function declaration.
We can then reference ...
as a value, and it returns all the extra arguments
in the same way that a function with multiple return values returned.
Here we create a log
function that prints all its arguments, but with
the text “LOGGING” in front.
log = (...) ->
print "LOGGING", ...
log "hello", "world"
We can do some more interesting things by putting ...
into table
which we will see below.
Tables
Tables are the universal data structure of MoonScript (and Lua). They are a combination of an array and a hash table. We may refer to an instance of a table as an object.
As Arrays
To create a simple array table we just list values separated by commas inside for a pair of curly braces.
We can then access the elements of a table using square brackets.
Notice that array tables are indexed starting at 1
(instead of 0
like you
might be familiar with in other languages).
x = { 1, 2, 3, "hello" }
print x[1], x[4]
If we try to print a table, we get something not so useful like table:
0x1c1f630
. Luckily, to we have a nifty built in function called unpack
that
we can use to quickly get all the values in an array table.
unpack
returns all the values in an array table as multiple return values:
y = { "unpack", "is", "cool" }
print y -- shows the address of the table in memory
print unpack y -- shows the contents of the table
The length operator, #
, can be used to get the size of a table:
y = { true, false, true, true, false }
print #y -- prints 5
Inside of curly braces, we are free to manipulate our whitespace. Commas can optionally be replaced by a newline as well.
identity_matrix = {
1, 0, 0
0, 1, 0
0, 0, 1
}
To remove something from a table, just set its value to nil
.
arr = { 5, 6, 2, 4, 5 }
-- remove the last item in the array
arr[#arr] = nil
print #arr -- prints 4
When you try to get a value from an index that doesn’t exist in a table, it
will return nil
.
As Hash Tables
In addition to numbers, we can index entries in a table on any other value. Including other tables.
moon_script = {
name: "MoonScript"
release_year: 2011
traits: { "dynamic", "interpreted" }
links: {
homepage: "http://moonscript.org"
reference: "http://moonscript.org/reference"
}
}
Alternatively we can leave off the curly braces, and use indentation. The same structure as above:
moon_script =
name: "MoonScript"
release_year: 2011
traits: { "dynamic", "interpreted" }
links:
homepage: "http://moonscript.org"
reference: "http://moonscript.org/reference"
We can stuff a hash on one line with or without curly braces, just separate the values by commas.
color = red: 255, green: 128, blue: 45
Looking up values who have string keys in the hash can be done using .
.
Likewise, we use the same syntax to assign to when updating values.
print moon_script.links.homepage
moon_script.name = "MoonScript!"
As mentioned above, we can use any value to index on. We use square brackets to enclose expressions that give use our values to index on.
x = {
["name"]: "MoonScript" -- same as `name:`
[(5 + 10)*2]: true
[->]: true
}
print x["name"] -- same as `x.name`
print x[->] -- prints nil. We've created two different functions!
If you index on anything other than a string, number, or boolean, then it is index on the address of the object. So although two empty function literals, or two empty tables mean the same thing, they are different objects.
a, b = {}, {}
assert a != b
As Both
Tables are not either a hash or an array, but they are both at the same time. We can combine the syntax to produce interesting results. For example, storing meta-data in an array:
hats = {
"top hat", "bowler", "wizard"
last_updated: "January 12th"
}
Later on when we look at iteration, we will see how you can only iterate through the array values so the hash values don’t get in in the way.
Control Structures
Conditionals
Most of these should be pretty familiar. Remember that indentation is used to describe blocks of code.
using_moonscript = true
if using_moonscript
print "Keep it up!"
else
print "go to moonscript.org"
-- or on one line
have_coins = false
if have_coins then print "Got coins" else print "No coins"
-- elseif
height = 12
if height < 4
print "You are short"
elseif height < 6
print "You are average"
else
print "You are very tall!"
We can also use the if statement as a value, say as an argument to a function. The last statement of each block of the if statement is used as the returned value.
msg = if using_moonscript
"yes"
else
"no"
Another type of syntax lets us decorate any line with in if statement by putting it at the end:
print "Hello!" if greet_user
If a conditional value is false
or nil
then it will take the failure path.
Any other value is seen as true. This includes the number 0
and the empty
string.
There is also a switch
statement:
print "You should wear:"
switch shirt_color
when "blue" then print "black slacks"
when "hawaian"
if in_hawaii
print "hula skirt"
else
print "brown corduroys"
else
print "blue jeans"
Looping
There are three looping constructs, the numeric for
, the generic for in
and
the while
loop.
Numeric for
let’s us specify a start number, end number, and optionally a
step size. The bounds are inclusive.
for i = 1, 10 -- 1, 2, ..., 10
print i
min, max = 2, 20
for k = min, max, 2 -- even numbers from min to max
print k
-- print items in array
arr = { "hello", "world" }
for j = 1, #arr
print arr[j]
Generic for in
lets us loop over an iterator. Lua has built in function called
ipairs
that creates an iterator for an array table:
Iterators can return any number of values which can be named in the for
statement. ipairs
yields the index and the value for each iteration.
arr = { 5, 6, 8, 6, 32 }
for i, v in ipairs arr
print v
ipairs
only gives us the array components of the table, not any hash
components. We can use the pairs
function if we want to iterate through both
array and hash values.
Because it is so common to loop over just the array values of a table, MoonScript provides a special syntax. This specialized form is also slightly faster because it skips the overhead of using an iterator.
We use the *
prefix operator.
for v in *arr
print v
When using the *
operator, we can optionally specify starting and ending
indices, and even a step size (just like our numeric for
loop). Using a
negative index here represents that many places from the last index.
-- iterators over the 2nd to 5th elements
for v in *arr[2,5] do print v
-- from the 2nd to the end
for v in *arr[2,] do print v
-- from the 2nd to the 2nd to last
for v in *arr[4, -1] do print v
-- starting from 1st, every other element
for v in *arr[,,2] do print v
Finally, there is while
, which works how you would expect:
remaining = 10
while remaining > 0
print "remaining:", remaining
remaining -= 1
List Comprehensions
Often we find ourselves writing short chunks of code to transform a arrays by simple expressions. A list comprehension let’s us write these idioms in a minimal amount of code.
Here is an example makes a new array containing each value doubled from the original.
arr = { 5, 6, 2, 9 }
doubled = [v * 2 for v in *arr]
The for in
loop here functions the same as the one described above.
We can also limit the included values using a when
clause.
-- return halves of numbers that are evenly divided by 2
numbers = { 7, 4, 3, 66, 3 }
halved = [v / 2 for v in *numbers when v % 2 == 0]
You can have any number of for in
and when
clauses in a comprehension.
Multiple for in
clauses are equivalent to nested loops:
a, b = {4, 5, 6}, { 9, 8, 8 }
coords = [{x,y} for x in *a for y in *b]
Table Comprehensions
When working with hash tables, we can use curly braces to write a comprehension that generates both keys and values. Separate the key and value by a comma.
tuples = { {"hello", "world"}, {"hi", "there"} }
tbl = {tup[1], tup[2] for tup in *tuples}
Object Oriented Programming
If we put a function inside of a table then we've created object oriented programming by giving that table a method. We can also store state in the table as well.
object = {
state: 100
say_hi: -> print "hello"
}
object.say_hi!
We say a method has a receiving object, which is the object that the method operates on. Unlike other languages, we need to manually pass around the receiving object. Luckily MoonScript gives us syntax for that.
If you've used JavaScript then you're probably aware that the variable named
this
implicitly carries the receiving object.
We use the backslash \
operator to say “pass the object that this
function was called on as the first argument.” Thus, we pass in the receiving
object as an argument. It’s customary to name this first argument self
.
Backslash is used in place of the final .
when accessing and calling a
function in a table.
Here’s an example:
counter = {
count: 0
increment: (self, amount=1) ->
self.count += amount
show: (self) ->
print "The count is:", self.count
}
counter\increment!
counter\increment 5
counter\show!
There are a few more things MoonScript does to help us with object oriented
programming. Because we frequently create functions whose first argument is
self
, a special function syntax can be used to do this for us.
The syntax is called fat arrow, and looks like this: =>
Additionally, because we frequently access fields in the table named self, we
can use the @
shorthand.
@hello
is equivalent to self.hello
and @fn 1, 2 ,3
is equivalent to self\fn 1, 2, 3
.
Here’s the object from above rewritten:
counter =
count: 0
increment: (amount=1) => @count += amount
show: => print "The count is:", @count
Classes
Now that you've seen object oriented programming in action, you're probably thinking of ways you can create your own functions for creating instances of classes of objects.
In a language as expressive as MoonScript, there are many ways to express something like that, so MoonScript comes with it’s own implementation to save you the trouble of using a library or re-implementing it in all of your projects.
A class is just a template for objects. It says “all objects that have this template should have access to the template’s methods and properties”.
We create a class like so:
class Person
new: (@name) =>
greet: => print "Hello my name is", @name
moon = Person "MoonScript"
moon\greet!
There are a couple interesting things going on here.
The body of the class declaration looks just like the hash table syntax. It works the same way.
The class name is called like a function in order to create a new instance. The class name is actually just a regular variable where the class object is placed. It’s value is a special table that can be called like a function.
The special new
method is used as the constructor.
In any function, an argument name can be prefixed with@
. This causes the
value for that argument to be assigned to self with that name. In our
example, this sets the name
as an instance variable.
If you loop throught the entries in the resulting person object you'll notice it won’t show all of its properties.
for key in pairs person
print key
Only name
will be printed. This is because instances and implemented using
metatables. The metatable of the instance governs how properties are looked
up in the object. When asked for a property, the table itself is first checked.
If it doesn’t have a value stored, then, if a metatable has been assigned to
the table it will be checked for a special __index
property.
The __index
can then be a table itself, and the process continues
recursively. This is how inheritance is implemented.
Inheritance
We use inheritance to share implementations of behaviour between similar classes. When a child class extends a parent class, the child class has access to all the properties and methods of the parent class.
In MoonScript the extends
keyword is used.
class Chef extends Person
new: (name, @dish) => super name
greet: =>
super!
print "I like to cook", @dish
super
is a special keyword. When called like a function, it calls the
function of the same name in the parent class.
In the example above, we redefined the constructor and the greet
method so we
use super
as a convenient way to run the parent’s implementation.
Property Access
In addition to functions, classes can hold any value as well. You have to be careful when declaring tables in the class instead of in the constructor.
class TestClass
number: 100
items: {}
a = TestClass!
b = TestClass!
a.number = 1212
a.items[1] = "hello"
print b.number -- prints 100
print unpack b.items -- prints "hello"
The values declared in the class are shared amongst all instances. If the shared value is mutable (like a table), then changes will be visible among all instances.
Values like numbers, strings, and booleans are not mutable. It’s important to notice the distinction between changing the value itself, and changing what is stored in the variable. Storing a new value in an instance variable is safe because it adds or modifies that property in the instance.
When we set a property of the same name on an instance, it shadows the value on the class because properties are looked up first in the instance before looking in the class.
Sometimes we want to share a object amongst all instances, so it’s good to know how it’s done.
Class Objects
Using the class
statement creates a new class object and assigns it to the
variable that is the name of the class. The class object is a plain old table
with a metatable that gives it some extra functionality.
As we've seen, we can create new instances by calling the class object like a function.
We can also access the properties defined for instances by their name.
class SomeClass
name: "class"
func: => print "hello"
print SomeClass.name
print SomeClass.func
We can get the declared name of the class with the special __name
property,
and if it extends anything the parent is accessible from __parent
. The
__base
property is the metatable used by the instances.
There is a distinction between the class object and the __base
. The __base
holds all the properties available for instances (created in the past or in the
future). The class object is an almost empty table. Assigning a new property to
the class object will not put it in the base, so it will not be available to
the instances. If we want to enhance classes after creation we do it through
the __base
.
The reason why we can access instance properties through the class object is because its metatable searches the base if nothing could be found in itself.
The class can also be retrieved from any instance using __class
:
class Hello
new: =>
assert @__class == Hello
Hello!
class World extends Hello
nil -- a body of nil means this class defines no implementation
World! -- this throws an error, because @__class is equal to World