Toit language basics#
This quickstart guide is inspired by Ruby in Twenty Minutes. It makes the assumption that you already have Toit installed on your machine.
Toit is an 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#
A local installation of Toit comes with support for running small programs directly from the command line. If you put the following code in a file called hello.toit
main:
print "Hello World!"
you can run it from the command line like this:
$ toit execute hello.toit
Hello World!
What just happened? The toit
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:
main:
print "Hello World!"
print "Hello World!"
When you run the updated program, you will see two lines of output:
$ toit execute hello.toit
Hello World!
Hello World!
Defining a method#
What if you want to say "Hello" a lot without getting your fingers all tired? You should define another method:
hi:
print "Hello World!"
and call that from main
:
main:
hi
hi
Calling a method in Toit is as simple as mentioning its name. If the method 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.
hi name:
print "Hello $name!"
This way, hi
is a method that takes a single argument. We can use that from main
:
main:
hi "Lars"
hi "Kasper"
and it works!
$ toit execute hello.toit
Hello Lars!
Hello Kasper!
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:
hi name = "World":
print "Hello, $name.trim!"
This way, we call the trim
method 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:
print "Hello, $(name.trim)!"
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:
main:
hi
hi "Kasper"
and get the following output:
$ toit execute hello.toit
Hello World!
Hello Kasper!
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. 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:
constructor .name = "World":
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:
main:
greeter := Greeter " Helena "
greeter.say_hi
greeter.say_bye
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:
$ toit execute hello.toit
Hi Helena!
Bye Helena, come back soon.
If you want to get the name from a greeter, you can ask a greeter by calling the name
method on it:
main:
greeter := Greeter " Helena "
print "How are you $(greeter.name)?"
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:
class Greeter:
name_:= null
constructor .name_ = "World":
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:
name: return name_.trim
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:
main:
greeter := Greeter " Erik "
print "How are you $(greeter.name)?"
and you should see How are you Erik?
. Inside main
, we store the greeter in a local variable (greeter
) so we can keep referring to the same object. You can think of it as a way to give a specific object a name that is only valid and useful inside the main
method.
Just like introducing a member variable, we can use the :=
syntax in methods and functions like main
to introduce local variables.
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:
class MegaGreeter:
names := []
constructor name = "World":
names.add name
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:
main:
greeter := MegaGreeter
print "The names are $greeter.names"
will lead to this output:
$ toit execute hello.toit
The names are ["World"]
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:
$ toit execute hello.toit
Hello World!
Bye World, come back soon.
Hello World!
Hello Lars!
Hello Kasper!
Hello Rikke!
Bye World, Lars, Kasper, Rikke, come back soon.
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:
// Greeter that says hi to everybody.
class MegaGreeter:
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:
class SimpleGreeter:
say_hi: print "Hi!" // Method all 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:
class MegaGreeter:
// class members start
// ...
// class members end
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:
say_hi:
// Greet everyone individually!
names.do: print "Hello $it!"
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 lists, 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
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
:
say_bye:
everyone := names.join ", "
print "Bye $everyone, come back soon."
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:
say_hi greeting = "Hello,":
// Greet everyone individually!
names.do: print "$greeting $it!"
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:
Kaixo, World!
Kaixo, Lars!
Kaixo, Kasper!
say_hi --greeting="Hello,":
// Greet everyone individually!
names.do: print "$greeting $it!"
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:
Hello, World!
Hej, World!
Hej, Lars!
Hej, Kasper!
If statements and basic expressions#
We can program a ridiculously inefficient Fibonnaci sequence generator using if
and recursion:
fib n:
if n <= 1: return n
return (fib n-1) + (fib n-2)
main:
print "The 10th Fibonnaci 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. We could have spaced it more conventionally and got the same effect:
fib n:
if n <= 1: return n
return (fib n - 1) + (fib n - 2)
Loops#
This is a terribly slow way to calculate a Fibonnaci number though, and we could do it with a simple loop:
fib2 n:
s1 := 0
s2 := 1
n.repeat:
s3 := s1 + s2
s1 = s2
s2 = s3
return s1
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 a magic variable, it
that gives the iteration number:
// Prints the numbers from 0 to n (exclusive).
print_n_numbers n:
n.repeat: print it
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 hashmap 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:
say_hi:
for i := 0; i < names.size; i++:
print "Hello, $names[i]"
Blocks#
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 perfectly normal methods on the List and Integer classes:
class List:
// ...
do [block]:
size.repeat: block.call this[it]
class Integer:
// ...
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
.
Blocks that return a value#
A block 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.