To do real work in a program you have to be able to make decisions, iterate through collections of items and perform actions repeatedly. For this, you need control structures. Pony has control structures that will be familiar to programmers who have used most languages, such as if
, while
and for
, but in Pony, they work slightly differently.
Conditionals
The simplest control structure is the good old if
. It allows you to perform some action only when a condition is true. In Pony it looks like this:
if a > b then
env.out.print("a is bigger")
end
Can I use integers and pointers for the condition like I can in C? No. In Pony if
conditions must have type Bool, i.e. they are always true or false. If you want to test whether a number a
is not 0, then you need to explicitly say a != 0
. This restriction removes a whole category of potential bugs from Pony programs.
If you want some alternative code for when the condition fails just add an else
:
if a > b then
env.out.print("a is bigger")
else
env.out.print("a is not bigger")
end
Often you want to test more than one condition in one go, giving you more than two possible outcomes. You can nest if
statements, but this quickly gets ugly:
if a == b then
env.out.print("they are the same")
else
if a > b then
env.out.print("a is bigger")
else
env.out.print("b bigger")
end
end
As an alternative Pony provides the elseif
keyword that combines an else
and an if
. This works the same as saying else if
in other languages and you can have as many elseif
s as you like for each if
.
if a == b then
env.out.print("they are the same")
elseif a > b then
env.out.print("a is bigger")
else
env.out.print("b bigger")
end
Why can’t I just say “else if” like I do in C? Why the extra keyword? The relationship between if
and else
in C, and other similar languages, is ambiguous. For example:
// C code
if(a)
if(b)
printf("a and b\n");
else
printf("not a\n");
Here it is not obvious whether the else
is an alternative to the first or the second if
. In fact here the else
relates to the if(b)
so our example contains a bug. Pony avoids this type of bug by handling if
and else
differently and the need for elseif
comes out of that.
Control structures are expressions
The big difference for control structures between Pony and other languages is that in Pony everything is an expression. In languages like C++ and Java if
is a statement, not an expression. This means that you can’t have an if
inside an expression, there has to be a separate conditional operator ‘?’.
In Pony control flow statements like this are all expressions that hand back a value. Your if
statement hands you back a value. Your for
loop (which we’ll get to a bit later) hands you back a value.
This means you can use if
directly in a calculation:
x = 1 + if lots then 100 else 2 end
This will give x a value of either 3 or 101, depending on the variable lots.
If the then
and else
branches of an if
produce different types then the if
produces a union of the two.
var x: (String | Bool) =
if friendly then
"Hello"
else
false
end
But what if my if doesn’t have an else? Any else
branch that doesn’t exist gives an implicit None
.
var x: (String | None) =
if friendly then
"Hello"
end
The same rules that apply to the value of an if
expression applies to loops as well. Let’s take a look at what a loop value would look like:
actor Main
new create(env: Env) =>
var x: (String | None) =
for name in ["Bob"; "Fred"; "Sarah"].values() do
name
end
match x
| let s: String => env.out.print("x is " + s)
| None => env.out.print("x is None")
end
This will give x the value “Sarah” as it is the last name in our list. If our loop has 0 iterations, then the value of its else
block will be the value of x. Or if there is no else
block, the value will be None
.
actor Main
new create(env: Env) =>
var x: (String | None) =
for name in Array[String].values() do
name
else
"no names!"
end
match x
| let s: String => env.out.print("x is " + s)
| None => env.out.print("x is None")
end
Here the value of x is “no names!”
actor Main
new create(env: Env) =>
var x: (String | None) =
for name in Array[String].values() do
name
end
match x
| let s: String => env.out.print("x is " + s)
| None => env.out.print("x is None")
end
And lastly, here x would be None
.
Loops
if
allows you to choose what to do, but in order to do something more than once, you want a loop.
While
Pony while
loops are very similar to those in other languages. A condition expression is evaluated and if it’s true we execute the code inside the loop. When we’re done we evaluate the condition again and keep going until it’s false.
Here’s an example that prints out the numbers 1 to 10:
var count: U32 = 1
while count <= 10 do
env.out.print(count.string())
count = count + 1
end
Just like if
expressions, while
is also an expression. The value returned is just the value of the expression inside the loop the last time we go round it. For this example that will be the value given by count = count + 1
when count is incremented to 11. Since Pony assignments hand back the old value our while
loop will return 10.
But what if the condition evaluates to false the first time we try, then we don’t go round the loop at all? In Pony while
expressions can also have an else
block. In general, Pony else
blocks provide a value when the expression they are attached to doesn’t. A while
doesn’t have a value to give if the condition evaluates to false the first time, so the else
provides it instead.
So is this like an else block on a while loop in Python? No, this is very different. In Python, the else
is run when the while
completes. In Pony the else
is only run when the expression in the while
isn’t.
Break
Sometimes you want to stop part-way through a loop and give up altogether. Pony has the break
keyword for this and it is very similar to its counterpart in languages like C++, C#, and Python.
break
immediately exits from the innermost loop it’s in. Since the loop has to return a value break
can take an expression. This is optional, and if it’s left out, the value from the else
block is returned.
Let’s have an example. Suppose you want to go through a list of names, looking for either “Jack” or “Jill”. If neither of those appear, you’ll just take the last name you’re given, and if you’re not given any names at all, you’ll use “Herbert”.
var name =
while moreNames() do
var name' = getName()
if name' == "Jack" or name' == "Jill" then
break name'
end
name'
else
"Herbert"
end
So first we ask if there are any more names to get. If there are then we get a name and see if it’s “Jack” or “Jill”. If it is we’re done and we break out of the loop, handing back the name we’ve found. If not we try again.
The line name'
appears at the end of the loop so that will be our value returned from the last iteration if neither “Jack” nor “Jill” is found.
The else
block provides our value of “Herbert” if there are no names available at all.
Can I break out of multiple, nested loops like the Java labeled break? No, Pony does not support that. If you need to break out of multiple loops you should probably refactor your code or use a worker function.
Continue
Sometimes you want to stop part-way through one loop iteration and move onto the next. Like other languages, Pony uses the continue
keyword for this.
continue
stops executing the current iteration of the innermost loop it’s in and evaluates the condition ready for the next iteration.
If continue
is executed during the last iteration of the loop then we have no value to return from the loop. In this case, we use the loop’s else
expression to get a value. As with the if
expression, if no else
expression is provided, None
is returned.
Can I continue an outer, nested loop like the Java labeled continue? No, Pony does not support that. If you need to continue an outer loop you should probably refactor your code.
For
For iterating over a collection of items Pony uses the for
keyword. This is very similar to foreach
in C#, for
..in
in Python and for
in Java when used with a collection. It is very different to for
in C and C++.
The Pony for
loop iterates over a collection of items using an iterator. On each iteration, round the loop, we ask the iterator if there are any more elements to process, and if there are, we ask it for the next one.
For example, to print out all the strings in an array:
for name in ["Bob"; "Fred"; "Sarah"].values() do
env.out.print(name)
end
Note the call to values()
on the array — this is because the loop needs an iterator, not an array.
The iterator does not have to be of any particular type, but needs to provide the following methods:
fun has_next(): Bool
fun next(): T?
where T is the type of the objects in the collection. You don’t need to worry about this unless you’re writing your own iterators. To use existing collections, such as those provided in the standard library, you can just use for
and it will all work. If you do write your own iterators, note that we use structural typing, so your iterator doesn’t need to declare that it provides any particular type.
You can think of the above example as being equivalent to:
let iterator = ["Bob"; "Fred"; "Sarah"].values()
while iterator.has_next() do
let name = iterator.next()?
env.out.print(name)
end
Note that the variable name is declared let, so you cannot assign to the control variable within the loop.
Can I use break and continue with for loops? Yes, for
loops can have else
expressions attached and can use break
and continue
just as for while
.
Repeat
The final loop construct that Pony provides is repeat
until
. Here we evaluate the expression in the loop and then evaluate a condition expression to see if we’re done or we should go round again.
This is similar to do
while
in C++, C# and Java, except that the termination condition is reversed; i.e. those languages terminate the loop when the condition expression is false, but Pony terminates the loop when the condition expression is true.
The differences between while
and repeat
in Pony are:
- We always go around the loop at least once with
repeat
, whereas withwhile
we may not go round at all. - The termination condition is reversed.
Suppose we’re trying to create something and we want to keep trying until it’s good enough:
actor Main
new create(env: Env) =>
var counter = U64(1)
repeat
env.out.print("hello!")
counter = counter + 1
until counter > 7 end
Just like while
loops, the value given by a repeat
loop is the value of the expression within the loop on the last iteration, and break
and continue
can be used.
Since you always go round a repeat loop at least once, do you ever need to give it an else expression? Yes, you may need to. A continue
in the last iteration of a repeat
loop needs to get a value from somewhere, and an else
expression is used for that.