Advanced

Control flow

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)
sizefromtoiota
3020
3021
3022

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))
toiota
62
63
64
65
66

Or passing a to (using a shorthand compose this time):

loop(to:=3)
fromsizeiota
040
041
042
043

Or passing a from and a to:

loop(from:=1, to:=5)
sizeiota
51
52
53
54
55

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}
ij
00
01
02
10
11
12
20
21
22
30
31
32
40
41
42

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:

  1. You may need to wrap the yield in a condition to avoid other logic also yielding
  2. If needed, you can yield more than once per set of arguments
If no yield is given, no tuple is issued. You can use 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