We can define a relation as a set of pre-defined tuples, and in Ra we can alternatively use code to generate the tuples.
Where we'd usually put the set of tuple literals (or set of tuple bodies if using the [...]
shorthand)
we instead put code to generate tuples between a begin
and end
block.
Such generators become especially useful if they are passed some of the attribute values as a starting point. Then they can perform useful calculations, behaving like functions, although functions that can:
- return multiple results
- be multidirectional depending on which attributes are joined/passed
plus
(though it may as well be named 'subtract').
plus := {x:int y:int z:int} begin
if given(x, y, z) then begin if x+y=z then begin yield end end
elseif given(x, y ) then begin z:=x+y; yield end
elseif given( y, z) then begin x:=z-y; yield end
elseif given(x, z) then begin y:=z-x; yield end
/* else no yield => false (too little to go on for now) */
end
The yield
command returns a tuple based on the current values that have been set for the heading attributes.
print(to_rel(tuple{x:=3, y:=4}) & plus) // what is 3 + 4?
┌───┬───┬───┐ │ x │ y │ z │ ├───┼───┼───┤ │ 3 │ 4 │ 7 │ └───┴───┴───┘
We can compose the two relations, instead of simply joining them, using parentheses:
print(to_rel(tuple{x:=3, y:=4})(plus)) // what is 3 + 4?
┌───┐ │ z │ ├───┤ │ 7 │ └───┘
We can compose them and pass arguments using a shorthand (for a single tuple only). It now looks more like a function call from a traditional language.
print(plus(x:=3, y:=4)) // what is 3 + 4?
┌───┐ │ z │ ├───┤ │ 7 │ └───┘
If need be, we can extract the single result from the relation.
print(plus(x:=3, y:=4)..) // what is 3 + 4?
7
And since we coded it to check the givens, we can call it many ways.
print(plus(y:=4, z:=7)) // what added to 4 makes 7? i.e. what is 7-4?
┌───┐ │ x │ ├───┤ │ 3 │ └───┘
Including checking the validity of an assertion (passing an argument for every parameter).
print(plus(x:=3, y:=4, z:=7) = dee) // does 3 + 4 equal 7?
// i.e. does it return a single-row relation with 0 attributes
true
print(plus(x:=3, y:=4, z:=70) = dee) // does 3 + 4 equal 70?
false
And unlike traditional functions, this one operates on whole sets at once - just rename the incoming attributes to match.
print(plus(SP{*, x:=QTY, y:=10}){*, QTY_ADD_TEN:=z})
┌─────┬─────┬─────────────┐ │ SNO │ PNO │ QTY_ADD_TEN │ ├─────┼─────┼─────────────┤ │ S1 │ P1 │ 310 │ │ S1 │ P2 │ 210 │ │ S1 │ P3 │ 410 │ │ S1 │ P4 │ 210 │ │ S1 │ P5 │ 110 │ │ S1 │ P6 │ 110 │ │ S2 │ P1 │ 310 │ │ S2 │ P2 │ 410 │ │ S3 │ P2 │ 210 │ │ S4 │ P2 │ 210 │ │ S4 │ P4 │ 310 │ │ S4 │ P5 │ 410 │ └─────┴─────┴─────────────┘
Obviously this is a contrived example. The better way would be to use the built-in + operator.
print(SP{*, QTY_ADD_TEN:=QTY+10}~{QTY})
┌─────────────┬─────┬─────┐ │ QTY_ADD_TEN │ SNO │ PNO │ ├─────────────┼═════┼═════┤ │ 110 │ S1 │ P5 │ │ 110 │ S1 │ P6 │ │ 210 │ S1 │ P2 │ │ 210 │ S1 │ P4 │ │ 210 │ S3 │ P2 │ │ 210 │ S4 │ P2 │ │ 310 │ S1 │ P1 │ │ 310 │ S2 │ P1 │ │ 310 │ S4 │ P4 │ │ 410 │ S1 │ P3 │ │ 410 │ S2 │ P2 │ │ 410 │ S4 │ P5 │ └─────────────┴─────┴─────┘
We can use such relation generators to build whole programs and systems, with data promoted to its proper place.