Language basics
This quick-start guide is inspired by Ruby in Twenty Minutes. It makes the assumption that you have Jaguar installed on your machine.
Toit is an open source, object-oriented programming language for the Internet of Things. The Toit language has the following desirable properties:
- Modern, simple, and approachable
- High-level and object-oriented
- Declarative and statically analyzable
- Safe and garbage collected
Now, let's get started with some programming!
Hello, World
The Toit SDK and Jaguar CLI both support running small programs directly from the command line.
If you put the following code in a file called hello.toit
:
you can run it from the command line like this:
What just happened? The jag
command line tool read your source code
(hello.toit
) and started running it from the main
method that you defined.
The main
method consists of all the indented statements just below the method
declaration line main:
. Toit is indentation-based like Python, so the spaces
you add to your programs are significant.
Once the program ran, it printed Hello World!
in your terminal. This is
because the only statement in hello.toit
is a method call, where you invoke
the print
method with a single argument, which is the string to
be printed (in this case to the terminal). If you wanted to output more than
one line from your program, you could update it to:
When you run the updated program, you will see two lines of output:
Defining a function
What if you want to say "Hello" a lot without getting your fingers all tired? You should define another function:
and call that from main
:
Calling a function in Toit is as simple as mentioning its name. If the function doesn't take arguments that's all you need.
What if we want to say hello to one person, and not the whole world? Just redefine hi to take a name as an argument.
This way, hi
is a function that takes a single argument. We can use that from main
:
and it works!
Inserting strings in strings
What's the $name
bit? That's Toit's way of inserting something into a string.
It is called string interpolation.
The bit after the $
is turned into a string (if it isn't one already) and
then substituted into the outer string at that point. You can also use this to
make sure that someone's name is properly trimmed so leading and trailing
whitespace is ignored:
This way, we call the trim
function on the name
string before we insert it
into the outer string. If we call hi " Lars "
we still get the familiar
greeting Hello Lars!
and not Hello Lars !
. You can add parentheses
around the name.trim
expression in the string to make it clearer which parts
belong to the outer string:
Maybe you already spotted that we went ahead and added one other trick to the
code above? We added a default value for the name
parameter, so if the name
isn't supplied when you call hi
, we use the default name "World". Now we can
try:
and get the following output:
Evolving into a greeter
What if we want a real greeter around, one that remembers your name and
welcomes you and treats you with respect. You might want to use an object for
that. Let's create a Greeter
class:
class Greeter: name := null constructor .name="World": say-hi: print "Hi $name.trim!" say-bye: print "Bye $name.trim, come back soon."
The new keyword here is class
. This defines a new class called Greeter
and
a bunch of methods for that class. Methods are just functions that are attached
to an object. Pay special attention to the method constructor
. There is
nothing after the :
and the constructor
method isn't followed by any
indented lines, so the constructor has no statements in it:
This is a
constructor
and it defines how you can construct objects from the class. It says the class
Greeter
takes a single argument (name
), but the .
prefix to the .name
parameter actually tells us that the name is immediately stored as a field on
Greeter
objects. The field is defined just above the constructor with the
:=
syntax.
The field parameter .name
still has a default value, so if we don't pass a name, the Greeter
will greet the world.
The say-hi
and say-bye
methods are introduced on the next two lines. The
methods both have a single statement in them, so we can keep them on one line
each. The say-hi
and say-bye
method both use the name
field from the
object they are called on. You can refer to fields in the class of a method
simply by mentioning them (name
).
Creating a greeter object
Now let's create a greeter object and use it:
We create an object simply by mentioning the constructor, Greeter
. The
greeter object remembers the name and uses it for the two separate greetings.
If we run this, we get the following output:
If you want to get the name from a greeter, you can ask a greeter by calling the name
method on it:
This would show How are you Helena ?
. Almost neat, right? Unfortunately, the
name isn't trimmed like we expected. Let's fix that!
Fields and methods
As you have just seen, a field on an object introduces a method with the same
name. If you wanted to hide a field from the outside world, you could make it
private. By convention, methods and fields that end with an underscore (_
)
are private and not supposed to be touched from the outside:
This removes the name
method from greeters, but if we really want to allow
accessing the name from the outside, we could reintroduce a getter with the
same meaning as before.
class Greeter: name_ := null constructor .name_="World": name: return name_ say-hi: print "Hi $name_.trim!" say-bye: print "Bye $name_.trim, come back soon."
Here we use the new keyword return
to specify the value a method returns. We
could make it slightly more interesting and trim it in the process:
In this way, access to the name from the outside also gets the trimming and we
can avoid having to manually call trim
when getting the name:
class Greeter: name_ := null constructor .name_="World": name: return name_.trim say-hi: print "Hi $name!" say-bye: print "Bye $name, come back soon."
We can check that it works by running:
and you should see How are you Erik?
. greeter
is a local variable, only visible in the
main
method. We declared it with the :=
syntax, just like we used :=
to declare
member variables in classes.
Greetings everyone!
This greeter isn't all that interesting though, it can only deal with one person at a time. What if we had some kind of MegaGreeter that could either greet the world, one person, or a whole list of people? Let's try to build that. We will start with a class definition:
So MegaGreeter objects have a list of names
. The names
field is initialized to the empty list ([]
). The body of the MegaGreeter
constructor adds the given name
argument to the end of the list of names.
Notice that this is different than using a .name
parameter that automatically
assigns to the field called name
. Mega greeters don't have a single name and
no name
field, so here the name
is just an ordinary parameter that we can
use in the body of the constructor. All in all, this code:
will lead to this output:
We can now go ahead and add greeter methods that show all the names:
// Greeter that says hi to everybody. class MegaGreeter: names := [] constructor name="World": names.add name say-hi: // Greet everyone individually! names.do: print "Hello $it!" say-bye: everyone := names.join ", " print "Bye $everyone, come back soon." main: greeter := MegaGreeter greeter.say-hi greeter.say-bye greeter.names.add "Lars" greeter.names.add "Kasper" greeter.names.add "Rikke" greeter.say-hi greeter.say-bye
If you run this, you'll get this output:
Let's dive into the new constructs in the next sections.
Comments and indentation
Not everything in your source files is meant to be run by the Toit compiler. Sometimes, it is nice just to add comments that explain interesting things related to your code. In the example in the last section, there were a few single line comments:
Such comments start with //
and tell the system to ignore the rest of the line.
You have already seen the use of indentation to give hierarchical structure to
your code. The general structure is that after a :
you can have a single
construct if it fits on one line:
or you can add a newline after the :
and let the following lines that are
indented relative to the outer construct be a sequence of inner constructs:
names := [] // Method delimited by indentation say-bye: everyone := names.join ", " print "Bye $everyone, come back soon."
For methods, we often refer to the inner constructs as the statements of a method or the body of a method. The preferred indentation for inner constructs is two spaces.
For a class, everything that is indented under the class declaration line belongs to the class. We call such things class members:
For methods in a class, the statements in them are nested one level further (two spaces) than the class members:
class MegaGreeter: // class members start // ... say-hi: // method body start // ... // method body end // ... // class members end
It is common to refer to such nested structure as block structure.
Iterating over lists
Let's return to the MegaGreeter
example and take a look at another place
where constructs are block structured. In the say-hi
method, we want to call
print
for every single name in the names
list. We can do
this by calling names.do
and provide the list of statements we want to run
for each element using block structure:
Here the statement is on a single line, so there is no need to use indentation.
When using names.do
, a method available on all
collections, the special variable
it
contains the individual elements from the list in
turn. If there are 5 elements in the names
list, we will call print
5 times
producing 5 separate lines of output.
You can play with the methods on list by modifying and running the sample below:
main: list := [ "Horse", "Fish", "Radish", "Baboon" ] print "There are $(list.size) elements in the list" print "Here they are:" list.do: print "Element = $it" print "Here they are (sorted):" list.sort --in-place list.do: print "Element = $it"
One of the methods on lists that is very useful when constructing strings is
join
. It produces a string from a list of strings by joining the parts and
adding a separator between them. We use this in the MegaGreeter
example to
produce a single comma-separated list of names for the single line output of
say-bye
:
Named arguments
Perhaps you don't always want to say "Hello", so you add an argument with a default value to the say-hi
method:
Now the user of your class can write:
main: greeter := MegaGreeter greeter.names.add "Lars" greeter.names.add "Kasper" greeter.say-hi "Kaixo,"
Which produces the output:
However, at the calling site it may not be clear what the argument "Kaixo" is for. We can make it clearer with a named argument:
Now we can use this with:
main: greeter := MegaGreeter greeter.say-hi greeter.names.add "Lars" greeter.names.add "Kasper" greeter.say-hi --greeting="Hej,"
which outputs:
If statements and basic expressions
We can program a ridiculously inefficient Fibonacci sequence generator using if
and recursion:
fib n: if n <= 1: return n return (fib n - 1) + (fib n - 2) main: print "The 10th Fibonacci number is $(fib 10)"
This defines a top-level function called fib
that is not a member of any
class. (We already saw main
, which is a top level function with a special
name.)
The fib
function is recursive, calling itself, and also makes use of a few
new features. The if-statement is well known from
other languages. In Toit it works by taking an expression and conditionally
evaluating a block. Like other blocks we could have used indentation to group
multiple lines.
Toit also has the usual array of infix operators, +
, -
, *
, /
, %
etc.
and the relational operators <
, <=
, >
, >=
, ==
and !=
. The
operators have higher precedence than function
arguments, so we had to group the calls in parentheses to get the desired
behavior. The high precedence is what makes the arguments for the recursive
invocation of fib
work.
Loops
This is a terribly slow way to calculate a Fibonacci number though, and we could do it with a simple loop:
Here we are using the repeat
method on numbers, which runs a block a given
number of times. Like for the do
method, there's an automatic variable, it
that gives the iteration number:
The repeat
method is simple and efficient, but sometimes we need something
more flexible, and for that we have the well-known while
and for
statements:
// Prints the odd numbers less than n. print-odd-numbers n: for i := 1; i < n; i += 2: print i // Returns if the Collatz conjecture is true. collatz n: while n > 1: if n % 2 == 0: n = n / 2 else: n = n * 3 + 1
Maps and sets
Perhaps we need titles for our greeters:
class MegaGreeter: names := [] titles := {:} constructor: add name title: names.add name titles[name] = title say-hi: // Greet everyone individually! names.do: print "Hello, $titles[it] $it!" main: greeter := MegaGreeter greeter.add "Lars" "Mr." greeter.add "Rikke" "Dr." greeter.add "Günter" "Herr Professor Doktor Doktor" greeter.say-hi
Here we use a hash map to store the appropriate title for each
name. The empty map is given by {:}
and we use []
to access the values for
each key. The empty set is {}
and we already met the empty list, []
. The
lookup syntax []
also works on lists, so instead of the 'do' method we could
have used:
Blocks and lambdas
We already saw the repeat
method on integers and the do
method on lists:
main: // Print the numbers from 1 to 10, one per line. 10.repeat: print it + 1 my-list := [1, 2, 3] // Print the elements in my-list, one per line. my-list.do: print it
Syntactically they look like they are built in to the language like if
and
for
, but they are actually normal methods on the List and int classes:
class List: // ... do [block]: size.repeat: block.call this[it] class int: // ... repeat [block]: for i := 0; i < this; i++: block.call i
They are making use of a feature called blocks. These are
snippets of code that can be passed down the stack as arguments to methods and
functions. At the call site we precede the block with a colon, ':
', and at
the function definition we surround the parameter name with square brackets,
'[]
'. Often, there is one block parameter, it is in the final position and
it is called block
.
If a callback needs to survive the scope in which it is defined, we can't use blocks. Instead, we can use lambdas:
The syntax for lambdas is the same as for blocks, except that the we use ::
instead of :
. The lambda above is a function that takes a single argument and
returns the sum of that argument and n
. We can use it like this:
This will print 15
.
Blocks and lambdas with multiple arguments
Blocks and lambdas can have parameters, just like methods. The parameters are
listed after the ::
or :
. Here is an example of a block with two parameters:
The block in the do
method has two parameters, key
and value
. The do
method will call the block with each key-value pair in the map.
Blocks and lambdas that return a value
A block or lambda can return a value each time it is run. This is used for example
in the filter
method on lists.
// Takes a list of words, and returns a new list with only the // words that are 5 characters or fewer. short-words words: return words.filter: it.size <= 5
The filter
method calls the block, it.size <= 5
for each element in the
original list, and returns a new list containing only the short words.
Note that there is no return
statement in the block. A block will return the
value of the last statement to the place where it was invoked with
block.call
. In this case there is only one statement, which is the
boolean expression it.size <= 5
.
If you use the return
keyword in a block then it returns from the syntactic
function or method in which it is written. Usually this will behave as you
would expect:
wheres-walter list: list.do: if it.starts-with "Walter ": return it return null main: print (wheres-walter ["Ib Michael", "Walter White", "Marie Curie"])
The return
keyword is inside a block that is passed to the do
method. When
the name that starts with "Walter "
is found we immediately return the full
name from the wheres-walter
function without continuing to iterate over the
list.