Ra code can use a number of control flow constructs. These are needed less often than in traditional programming languages, since they can be replaced with relational operators in many cases.
Code blocks in Ra are surrounded by
begin
and
end
(like
Pascal) rather than the more C-like
{
and
}
.
This is because
{
and
}
are used instead for sets, and so used to build relations, tuples and sets of attributes etc.
Conditionals
Standard
if
/else
is supported, along with
elseif
for multiple tests.
a := 7
if a > 5 begin
print("a is large")
end
if tuple{SNO:="S2", PNO:="P2", QTY:=400} ∈ SP begin
print("exists")
end else begin
print("does not exist")
end
if a > 10 begin
print("a is very large")
end elseif a > 5 begin
print("a is large")
end else begin
print("a is small")
end
Loops
The
while
loop handles explicit iteration but, before using it, consider how a join could be used instead.
i := 0
while i < 7 begin
print("iteration", i)
i := i + 1
end
The
while
loop can be broken out of using
break
or you can skip to the next iteration (if there is to be one) using
continue
.
i := 0
while i < 7 begin
if i = 2 begin
continue
end
print("iteration", i)
if i > 4 begin
break
end
i := i + 1
end
loop
There is a built-in
loop
relation generator which can sometimes remove the need for a while loop.
It has a number of optional parameters:
from
,
to
,
size
and effectively generates
iota
.
You can pass one or more of these to generate a loop relation of the desired size.
For example, passing a size:
loop & dee(size:=3)
size | from | to | iota |
---|---|---|---|
3 | 0 | 2 | 0 |
3 | 0 | 2 | 1 |
3 | 0 | 2 | 2 |
For example, passing a size and a from (using compose this time, which removes our parameters from the results):
loop(dee(size:=5, from:=2))
to | iota |
---|---|
6 | 2 |
6 | 3 |
6 | 4 |
6 | 5 |
6 | 6 |
Or passing a to (using a shorthand compose this time):
loop(to:=3)
from | size | iota |
---|---|---|
0 | 4 | 0 |
0 | 4 | 1 |
0 | 4 | 2 |
0 | 4 | 3 |
Or passing a from and a to:
loop(from:=1, to:=5)
size | iota |
---|---|
5 | 1 |
5 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
Notice how the
iota
is the interesting result, so we can take.
loop(size:=3){iota}
iota |
---|
0 |
1 |
2 |
And from there rename iota and join it with other generators or operators.
Here's an example of a nested loop:
loop(size:=5){i:=iota} * loop(size:=3){j:=iota}
i | j |
---|---|
0 | 0 |
0 | 1 |
0 | 2 |
1 | 0 |
1 | 1 |
1 | 2 |
2 | 0 |
2 | 1 |
2 | 2 |
3 | 0 |
3 | 1 |
3 | 2 |
4 | 0 |
4 | 1 |
4 | 2 |
Yield
Inside a relation generator,
yield
will issue a tuple comprising the curent values of the parameters.
The control will then continue through the rest of the generator code, meaning:
- You may need to wrap the yield in a condition to avoid other logic also yielding
- If needed, you can yield more than once per set of arguments
given
to check which parameters, if any, were passed in.
Ideally, a relation generator should handle all combinations of parameters being passed.
If none are passed, it's usually flagged as an error.
If all are passed, the generator should yield if they satisfy the relation's predicate and not yield otherwise (effectively the responses for true and false).
relgen := {x:int, y:int} begin
if given(x, y) begin
if y = x * 2 begin
yield // => true
// else no yield => false
end
end elseif given(x) begin
y := x * 2
yield
end elseif given(y) begin
x := y * 0.5
yield
end
end
Return
Inside a relation operator,
return
will return an expression as the result.
It must match the output type.
As with relation generators, you can use
given
to check which parameters, if any, were passed in, though all of them will typically be passed.
userop := op(r:{*}, s:{*}) {attrs(heading(r) & heading(s))} begin
return r & s
end