These are the formal definitions of an Extended Relational Algebra in a style similar to Algebra A in Appendix A of DTATRM by D&D. The operators of this ERA are used to construct shorthands, intended to form the basis of a relational programming language. It should be noted that Appendix A takes the reduction path from language shorthand to algebra operation. Here the path is taken in the opposite direction, defining algebraic operations from which the shorthands of a language may be constructed.
These operators do not provide or require any particular type system. Any use of the algebra is dependent on an external provider of a type system and functions over those types. Shorthands such as GROUP and UNGROUP, WRAP and UNWRAP require that relations and tuples be provided by that type system.
The naming conventions are adapted from Appendix A. In what follows:
A is an attribute name
T is a type
v is a value
<A,T> is an attribute (member of a heading)
<A,T,v> is an attribute value (member of a tuple)
r is a relation
Hr is the heading of r
Br is the body of r
tr is a tuple of r
These are the basic 5 definitions adapted from Appendix A.
Given a relation r then s = NOT(r) is defined as follows.
Hs = Hr
Bs = { ts : ∃ tr ∉ Br, ts = tr
The NOT operator yields the complement s of a given relation r. The heading of s is the heading of r. The body of s contains every tuple with that heading that is not in the body of r.
The NOT operator is used in shorthands that involve negation, such as ANTIJOIN, MINUS.
Given a relation r and a type T such that <A,T> ∈ Hr then s = REMOVE(A) is defined as follows.
Hs = Hr minus { <A,T> }
Bs = { ts : ∃ tr ∉ Br, ∃ v ∈ T, <A,T,v> ∈ tr, ts = tr minus { <A,T,v> }
The REMOVE operator yields a relation s formed by removing a given attribute A from a given relation r. The operation is equivalent to taking the projection of r over all of its attributes except A. The heading of s is the heading of r minus the ordered pair <A,T>. The body of s contains every tuple that conforms to the heading of s and is a subset of some tuple of r.
The REMOVE operator is used in shorthands that remove one or more attributes, such as PROJECT.
Given a relation r, a type T such that <A,T> ∈ Hr and no type T such that <B,T> ∈ Hr then s = RENAME(A,B) is defined as follows.
Hs = ( Hr minus { <A,T> } ) union { <B,T> }
Bs = { ts : ∃ tr ∈ Br, ∃ v ∈ T, <A,T,v> ∈ tr,
ts = ( tr minus { <A,T,v> } ) union { <B,T,v> } }
The RENAME operator yields a relation s that differs from a given relation r only in the name of one of its attributes, which is changed from A to B. The heading of s is the heading of r except that the ordered pair <A,T> is replaced by the ordered pair <B,T>. The body of s consists of every tuple of the body of r, except that in each such tuple the triple <A,T,v> is replaced by the triple <B,T,v>.
The RENAME operator is used in shorthands that rename one or more attributes, such as RENAME.
Given relations r1 and r2 such that <A,T1> ∈ Hr1 and <A,T2> ∈ Hr2 implies T1 = T2 then s = AND(r1,r2) is defined as follows.
Hs = Hr1 union Hr2
Bs = { ts : ∃ tr1 ∈ Br1, ∃ tr2 ∈ Br2, ts = tr1 union tr2 }
The AND operator is relational conjunction, which is usually known as a natural join. The heading of s is the union of the headings of r1 and r2. The body of s contains every tuple that conforms to the heading of s and is a superset of both some tuple in the body of r1 and some tuple in the body of r2.
The AND operator is used in join shorthands such as JOIN, SEMIJOIN, ANTIJOIN, COMPOSE, TIMES.
Given relations r1 and r2 such that <A,T1> ∈ Hr1 and <A,T2> ∈ Hr2 implies T1 = T2 then s = OR(r1,r2) is defined as follows.
Hs = Hr1 union Hr2
Bs = { ts : ∃ tr1, ∃ tr2, ( tr1 ∈ Br1 or tr2 ∈ Br2), ts = tr1 union tr2 }
The OR operator is relational disjunction, being a generalization of what is usually known as union. In the special case where the given relations r1 and r2 have the same heading, the result s is in fact the union of those two relations in the traditional sense. The heading of s is the union of the headings of r1 and r2. The body of s contains every tuple that conforms to the heading of s and is a superset of either some tuple in the body of r1 or some tuple in the body of r2.
The OR operator is used in set shorthands such as UNION, MINUS, SYMDIFF, INSERT.
This is the formalisation of a relational constant or ‘relcon’, and relies on some previously defined function f. Separate formalisations are provided for monadic and dyadic functions, in both predicate and value-returning forms. Extending them to n-adic functions is left as an exercise.
Given attribute names X, types Tx and function f with the type signature Tx->Boolean then s = RELCON(X,f) is defined as follows.
Hs = {<X,Tx>}
Bs = { ts : ∃ vx ∈ Tx, f(vx) = true, ts = {<X,Tx,vx>} }
Given attribute names X,Y, types Tx,Ty and function f with the type signature Tx->Ty then s = RELCON(X,Y,f) is defined as follows.
Hs = {<X,Tx>, <Y,Ty>}
Bs = { ts : ∃ vx ∈ Tx, ∃ vy ∈ Ty, f(vx) = vy,
ts = {<X,Tx,vx>, <Y,Ty,vy>} }
Given attribute names X,Y, types Tx,Ty and function f with the type signature Tx->Ty->Boolean then s = RELCON(X,Y,f) is defined as follows.
Hs = {<X,Tx>, <Y,Ty>}
Bs = { ts : ∃ vx ∈ Tx, ∃ vy ∈ Ty, f(vx,vy) = true),
ts = {<X,Tx,vx>, <Y,Ty,vy} }
Given attribute names X,Y,Z, types Tx,Ty,Tz and function f with the type signature Tx->Ty->Tz then s = RELCON(X,Y,Z,f) is defined as follows.
Hs = {<X,Tx>, <Y,Ty>, <Z,Tz>}
Bs = { ts : ∃ vx ∈ Tx, ∃ vy ∈ Ty, ∃ vz ∈ Tz, f(vx,vy) = vz),
ts = {<X,Tx,vx>, <Y,Ty,vy, <Z,Tz,vz>} }
[Note: for the relcon PLUS in App-A, types Tx, Ty and Tz are all INTEGER, and the function f is the scalar operator “+”.]
The RELCON operator predicate forms are used together with AND in shorthands such as WHERE/RESTRICT.
The RELCON operator value-returning forms are used together with AND in shorthands such as EXTEND, SUMMARIZE, UPDATE and DELETE.
This is the formalisation of simple aggregation, similar to SUM, MAX or MIN but allowing for a wider range of functions. It relies on some previously defined aggregating function f. An aggregating function has an argument which is a list of values (a ‘bag’ or ‘multiset’ rather than a ‘set’) and returns a single value. Extending this approach to more complex aggregations is left as an exercise.
Given a relation r, a type T such that <A,T> ∈ Hr and an aggregating function f with the type signature T[]->Ta then s = AGGREGATE(r,A,f) is defined as follows.
Hs = ( Hr minus {<A,T>} ) union {<A,Ta>}
Bs = { ts : ∃ v ∈ T, ∃ tr ∈ r, ∃ <A,T,v> ∈ tr, v ∈ v[],
ts = tr minus {<A,T,v>} union {<A,Ta,f(v[])} }
Note: the term v[] should be read as: a list of the values of v for some value of ts.
The AGGREGATION operator is used in shorthands such as SUMMARIZE.
As transitive closure cannot be defined directly in set-builder notation, this formalisation defines it indirectly by means of a recurrence relation. This consists of a starting value (given) and a sequence of successors (defined by set-builder). The transitive closure is the fix-point union of that sequence.
The starting value (‘seed’) represents known edges in a directed graph; the end value is all the possible paths through the graph.
Given a relation r with the heading {<A,T>,<B,T>} for some type T then the successor relation r’ is defined as follows.
Hr’ = Hr
Br’ = { tr’ : tr’ ∈ Br or
∃ v1 ∈ T, ∃ v2 ∈ T, ∃ v3 ∈ T, ∃ v4 ∈ T,
∃ tr1 ∈ Br, tr1 = {<A,T,v1>, <B,T,v2>},
∃ tr2 ∈ Br, tr2 = {<A,T,v2>, <B,T,v3>},
tr’ = {<A,T,v1>, <B,T,v3>} }
Then the transitive closure s = TCLOSE(r) is defined as follows.
S = r^{1} U r^{2} U r^{3} … r^{¥}
This is a linear recurrence, which can be shown to reach a fix-point termination.
The TCLOSE operator is used in a shorthand of the same name.
In this case each tuple is associated with a value, and this definition relies on some previously defined dyadic function f that takes values of that type as its argument and return value. The value computed represents in some sense the cost of that path.
Given a relation r with heading {<A,T>,<B,T>,<C,Tv>} for some types T and Tv, and a dyadic function f with the type signature Tv->Tv->Tv then the successor relation r’ is defined as follows.
Hr’ = Hr
Br’ = { tr’ : tr’ ∈ Br or
( ∃ v1 ∈ T, ∃ v2 ∈ T, ∃ v3 ∈ T, ∃ v4 ∈ T,
∃ w1 ∈ Tv, ∃ w2 ∈ Tv,
∃ tr1 ∈ Br, tr1 = {<A,T,v1>, <B,T,v2>, <C,Tv,w1>},
∃ tr2 ∈ Br, tr2 = {<A,T,v2>, <B,T,v3>, <C,Tv,w2>},
tr’ = {<A,T,v1>, <B,T,v3>, <C,Tv,f(w1,w2)} ) }
Then the extended transitive closure s = ETCLOSE(r,f) is defined as follows.
s = r^{1} U r^{2} U r^{3} … r^{¥}
This is a linear recurrence, which can be shown to reach a fix-point termination.
The ETCLOSE operator is used in combination with AGGREGATE in a shorthand to perform Generalised Transitive Closure as defined by D&D (RM VSS 5).
]]>Each operation is a function with one or two relation values as arguments and returns one as its result. Each operation is generic, and has to be specialised for each use. It is specialised by:
Each operation returns a value with a heading determined entirely by the specialisation as set out above. The ERA depends on a library of pre-defined functions that are compatible with the types of the various attributes. The library and those types are specified separately. The ERA has type system, and no means to access tuple or attribute values.
The ERA also includes an assignment operation, which updates the value of a pseudo-variable, identified by a literal value.
Operator | Specialisers | Description |
select<args,bfn>(r) | Arguments to Boolean function | Restricts set membership |
remove<name>(r) | An attribute to remove | Removes an attributes |
rename<name1,name2>(r) | An attribute and its new name | Renames one attribute |
extend<args,fn,name>(r) | Arguments to function, name for new value | Appends one attribute |
replace<args,fn,name>(r) | Arguments to function, name of replaced value | Replaces the value of one attribute |
join(r1,r2) | Auto for join | Natural join |
semijoin(r1,r2) | Auto for join | Natural semijoin |
antijoin(r1,r2) | Auto for join | Natural antijoin |
compose(r1,r2) | Auto for join | Join removing join attributes |
union(r1,r2) | None | Set union |
minus(r1,r2) | None | Set minus |
intersect(r1,r2) | None | Set intersection |
difference(r1,r2) | None | Set difference |
aggregate<names,arg,afn>(r) | Grouping attributes, argument to aggregating function | Replace one attribute by grouped aggregation |
while<rfn>(r) | Relational function | Fixed point recursion |
var<lit> | Literal name of pseudo-variable | Reference to a stored value |
assign<lit>(r) insert<lit>(r) delete<lit,args,bfn>(r) replace<lit,args,fn,name>(r) | Literal name of pseudo-variable Arguments to Boolean function Arguments to function, name of replaced value | Update a stored value |
AndlRaKnime provides a set of nodes that implement the Relational Algebra. Using these nodes allows you to perform SQL-like queries on a wide variety of non-SQL data sources, such as CSV files or data retrieved online. For more about the Relational Algebra see here: https://en.wikipedia.org/wiki/Relational_algebra.
The set of nodes is currently:
For more about JEXL see here: http://commons.apache.org/proper/commons-jexl/.
Sample workflows are included to demonstrate each of these these capabilities. Here is an example.
An early version of AndlRaKnime has been released for comment and feedback.
You can find it here: https://github.com/david-pfx/AndlRaKnime
]]>The intention here is to paraphrase, generalise and shorten the wording and to use terminology familiar to a general IT audience, while retaining the original intent, meaning and numbering. This has involved some reorganisation and some inclusion of background material from other writings to ensure that the all the terms used can be understood within one document.
Read more…
]]>Andl.NET can perform relational queries at or beyond the capabilities
of any SQL dialect. It can do all the ordinary things like select, where
and join but it can also do generative queries, self-joins, complex
aggregations, subtotals and running totals (a bit like SQL recursive
common table expressions and windowing).
Andl.NET has its own in-memory database so it can provide a complete
application backend for any kind of user interface on any platform. It can
easily be used to program a data model as a set of tables just like SQL, but
including all the access routines, without the need for an Object Relational
Mapper.
Andl.NET can retrieve data from Csv, Txt, Sql, Odbc and Oledb but does not
provide any persistence mechanism. (Left as an exercise for the caller!)
The core feature of Andl.NET is an implementation of the generic interface
IRelatable<T>. This provides a series of extension methods similar in flavour
to Linq’s IEnumerable<T> and IQueryable<T>, but directly implementing the core
features of the Relational Algebra.
The main differences from SQL are:
* all joins are natural (by attribute name)
* relations (tables) have no duplicate tuples (rows)
* data values cannot be null.
The main differences from Linq are:
* provides sources, stores and updates as well as queries
* many additional relational operations
Sample programs are included to demonstrate these capabilities. Familiarity
with Linq will help in reading them.
A future release of Andl.NET will generate SQL so that queries can be
executed on a relational database backend.
The release is here.
The licence is Licence.
]]>Here is the source code. It should be self-explanatory. The original is here: https://web.njit.edu/~hassadi/Dbase_Courses/CIS631/Ex_03.html
//---------------------------------------------------------------------- // Q1. Get suppliers names who supply part 'P2'. // SQL>select sname from s, sp where s.s#=sp.s# and sp.p#='P2'; // SQL>select distinct s.sname from s where s.s# IN (select sp.s# from sp where sp.p# ='P2'); Show("Q1. Get suppliers names who supply part 'P2'", SP.Where(sp => sp.Pno == "P2") .Join(S, (sp, s) => new { s.Sname }) .AsEnumerable() .Select(s => $"{s.Sname}") ); Show("Q1. Get suppliers names who supply part 'P2'", S .Where(s => SP .Where(sp => sp.Pno == "P2") .Select(sp => new { sp.Sno }) .Contains(new { Sno = s.Sno })) .AsEnumerable() .Select(s => $"{s.Sname}") );
//---------------------------------------------------------------------- // Q2. Get suppliers names who supply at least one red part. // SQL>select distinct sname from s, sp, p where p.color='Red' and s.s#=sp.s# and p.p#=sp.p# // SQL>select distinct s.sname from s where s.s# IN (select sp.s# from sp where sp.p# IN (select p.p# from p where p.Color = 'Red') ); Show("Q2. Get suppliers names who supply at least one red part", P .Where(p => p.Color == "Red") .Join(SP, sp => new { sp.Sno }) .Join(S, s => new { s.Sname }) .AsEnumerable() .Select(s => $"{s.Sname}") ); Show("Q2. Get suppliers names who supply at least one red part", S.Where(s => SP .Where(sp => P .Where(p => p.Color == "Red") .Select(p => new { p.Pno }) .Contains(new { sp.Pno })) .Select(sp => new {sp.Sno}) .Contains(new { s.Sno})) .AsEnumerable() .Select(s => $"{s.Sname}") ); // SQL> select distinct s.sname from s where s.s# IN (select sp.s# from sp where sp.p# IN (select p.p# from p where p.Color = 'Red') ); //---------------------------------------------------------------------- // Q3. Get the supplier names for suppliers who do not supply part ‘P2’. // SQL> select distinct s.sname from s where s.s# NOT IN (select sp.s# from sp where sp.p#='P2'); // SQL> select distinct s.sname from s where NOT EXISTS (select * from sp where sp.s# = s.s# and sp.p# = 'P2'); Show("Q3. Get the supplier names for suppliers who do not supply part ‘P2’", S .Antijoin(SP .Where(sp => sp.Pno == "P2") , sp => new { sp.Sname }) .AsEnumerable() .Select(s => $"{s.Sname}") ); Show("Q3. Get the supplier names for suppliers who do not supply part ‘P2’", S .Where(s => (SP .Where(sp => sp.Sno == s.Sno && sp.Pno == "P2")) .IsEmpty()) .AsEnumerable() .Select(s=>$"{s.Sname}") ); //---------------------------------------------------------------------- // Q4. Get the supplier names for suppliers who supply all parts. // SQL> select distinct s.sname from s where NOT EXISTS // (select * from p where NOT EXISTS // (select * from sp where sp.s# = s.s# and sp.p# = p.p#) ); Show("Q4. Get the supplier names for suppliers who supply all parts", S .Where(s => P.Select(p => new { p.Pno }) .Equals(S.Where(sx => sx.Sno == s.Sno) .Join(SP, sp => new { sp.Pno }))) .AsEnumerable() .Select(s => $"{s.Sname}") ); Show("Q4. Get the supplier names for suppliers who supply all parts", S .Where(s => P .Where(p => SP .Where(sp => sp.Sno == s.Sno && sp.Pno == p.Pno) .IsEmpty()) .IsEmpty()) .AsEnumerable() .Select(s => $"{s.Sname}") ); //---------------------------------------------------------------------- //Q5. Get supplier numbers who supply at lease one of the parts supplied by supplier ‘S2’. //SQL> select distinct s.s# from s, sp where s.s# = sp.s# and p# IN (select p# from sp where sp.s# = 'S2') Show("Q5. Get supplier numbers who supply at lease one of the parts supplied by supplier ‘S2’", S .Join(SP, (s, sp) => new { s.Sno, sp.Pno }) .Semijoin(SP .Where(sp => sp.Sno == "S2") .Select(sp => new { sp.Pno })) .Select(s => new { s.Sno }) .AsEnumerable() .Select(s => $"{s.Sno}") ); //---------------------------------------------------------------------- // Q6. Get all pairs of supplier numbers such that two suppliers are “colocated” (located in the same city). // SQL> select A.s# AS SA, B.S# AS SB from S A, S B where A.city = B. city and A.s# < B.S# Show("Q6. Get all pairs of supplier numbers such that two suppliers are “colocated” (located in the same city)", S.Select(s => new { SA = s.Sno, s.City }) .Join(S .Select(s => new { SB = s.Sno, s.City }) , (sa,sb)=> new { sa.SA, sb.SB }) .Where(s => s.SA.CompareTo(s.SB) < 0) .AsEnumerable() .Select(s => $"{s.SA} {s.SB} ") ); //---------------------------------------------------------------------- // Q7. Join the three tables and find the result of natural join with selected attributes. // SQL> select distinct s.s#, sname, p.p#, p.pname, s.city, status, QTY from s, sp, p where s.s#=sp.s# and p.p#=sp.p# and s.city=p.city Show("Q7. Join the three tables and find the result of natural join with selected attributes", S.Join(SP, (s, sp) => new { s.Sno, s.Sname, s.City, s.Status, sp.Pno, sp.Qty }) .Join(P, (ssp, p) => new { ssp.Sno, ssp.Sname, ssp.City, ssp.Status, ssp.Pno, ssp.Qty, p.Pname }) .AsEnumerable() .Select(t => $"{t.Sno} {t.Sname} {t.Pno} {t.Pname} {t.City} {t.Status} {t.Qty}") ); //---------------------------------------------------------------------- // Q8. Get all shipments where the quantity is in the range 300 to 750 inclusive. // SQL> select spj.* from spj where spj.QTY>=300 and spj.QTY<=750; Show("Q8. Get all shipments where the quantity is in the range 300 to 750 inclusive", SPJ.Where(spj => spj.Qty >= 300 && spj.Qty <= 750) .AsEnumerable() .Select(t => $"{t.Sno} {t.Pno} {t.Jno} {t.Qty}") ); //---------------------------------------------------------------------- // Q9. Get all supplier-number/part-number/project-number triples such that the indicated supplier, part, and project are all colocated (i.e., all in the same city). // SQL> select s.s#, p.p#, J.j# from s, p, j where s.city = p.city and p.city = j.city; // NOTE: mismatch -- 16 rows instead of 12, but looks OK Show("Q9. Get all supplier-number/part-number/project-number triples such that the indicated supplier, part, and project are all colocated (i.e., all in the same city)", S.Join(P, (s,p) => new { s.Sno, s.City, p.Pno }) .Join(J, (sp, j) => new { sp.Sno, sp.Pno, j.Jno }) .AsEnumerable() .Select(t => $"{t.Sno} {t.Pno} {t.Jno}") ); //---------------------------------------------------------------------- // Q10. Get all pairs of city names such that a supplier in the first city supplies a project in the second city. // SQL> select distinct s.city as scity, j.city as jcity from s, j where exists (select * from spj where spj.s# = s.s# and spj.j# = j.j#); Show("Q10. Get all pairs of city names such that a supplier in the first city supplies a project in the second city", S .Select(s => new { s.Sno, Scity = s.City }) .Join(J .Select(j => new { j.Jno, Jcity = j.City }) , (s, j) => new { s.Sno, s.Scity, j.Jno, j.Jcity }) .Where(sj => SPJ .Where(spj => spj.Sno == sj.Sno && spj.Jno == sj.Jno) .Exists()) .Select(sj => new { sj.Scity, sj.Jcity }) .AsEnumerable() .Select(t => $"{t.Scity} {t.Jcity}") ); //---------------------------------------------------------------------- // Q11. Get all cities in which at least one supplier, part, or project is located. // SQL> select s.city from s union select p.city from p union select j.city from j; Show("Q11. Get all cities in which at least one supplier, part, or project is located", S.Select(s => new { s.City }) .Union(P .Select(p => new { p.City })) .Union(J .Select(j => new {j.City})) .AsEnumerable() .Select(t => $"{t.City}") ); //---------------------------------------------------------------------- // Q12. Get supplier-number/part-number pairs such that the indicated supplier does not supply the indicated part. // SQL> select s.s#, p.p# from s, p minus select spj.s#, spj.p# from spj; Show("Q12. Get supplier-number/part-number pairs such that the indicated supplier does not supply the indicated part", S .Select(s => new { s.Sno }) .Join(P, (s, p) => new { s.Sno, p.Pno }) .Minus(SPJ .Select(spj => new { spj.Sno, spj.Pno })) .AsEnumerable() .Select(t => $"{t.Sno} {t.Pno}") ); //---------------------------------------------------------------------- // Q13. Get all pairs of part numbers and supplier numbers such that some supplier supplies both indicated parts. // SQL> select distinct spjx.s#, spjx.p# as PA, spjy.p# as PB from spj spjx, spj spjy where spjx.s# = spjy.s# and spjx.p# < spjy.p#; Show("Q13. Get all pairs of part numbers and supplier numbers such that some supplier supplies both indicated parts", SPJ.Select(spj => new { spj.Sno, PA=spj.Pno }) .Join(SPJ, (spj1, spj2) => new { spj1.Sno, spj1.PA, spj2.Pno }) .Where(spj => spj.PA.CompareTo(spj.Pno) < 0) .AsEnumerable() .Select(t => $"{t.Sno} {t.PA} {t.Pno}") );]]>
The code looks like this:
class TT1 : NdlTuple<TT1> { public string A2; public int A1; public decimal A3; public static TT1[] R1() { return new TT1[] { new TT1 { A1 = 42, A2 = "hello", A3 = 1.2m }, new TT1 { A1 = 42, A2 = "world", A3 = 1.2m }, }; } public static NdlRelation<TT1> NDR_1() { return new NdlRelation<TT1>(R1()); } } var v1 = TT1.NDR_1(); var v2 = v1.Where(t => t.A1 == 42); var v3 = v2.Select(t => new TT2 { A1 = t.A1, A2 = "constant", A3 = t.A3 });
I’m making heavy use of NUnit and finding plenty of bugs. No updates yet; no data import; no aggregation; no while/recursion; no ordered queries. But it all seems surprisingly possible, even without a lot of help from the compiler. And much easier than writing compilers.
]]>In my opinion the TTM/D type system can be substantially reproduced as follows.
Tuple types are the core of the system. In the absence of compiler support, every tuple type consumed or emitted by any relational operator has to be individually declared, but the common functionality comes from inheriting a base class and related interfaces.
Relation types are interesting. A lengthy sequence of operations of the Relational Algebra is just a set of chained functions calls and enumerators. It does not in fact cause any processing of data until a caller either (a) retrieves data to be sent elsewhere or (b) commits a transaction. A relvar is a relation type from which an enumerator can be obtained and which accepts updates. It is not a collection, just an adapter to someone else’s collection.
I have a crude but working implementation (and I have learned a lot about C# generics). Still hard to tell whether it leads anywhere, but I should be able to hook it up to the Andl database backend and then we’ll see. An Sql implementation can then generate Sql and enumerate the result set instead of the raw data (Andl does that).
]]>
The Relational Algebra can be seen as dealing with streams of tuples, where
Consider the implementation of a simple algorithm such as Union:
Possibilities:
In C# much of the implementation requires reflection. The Andl implementation is similar to (3).
]]>A lambda is the literal form of a value of type ‘function’. It corresponds to a defined function but can be treated as a first class value: it can be
The syntactic definition is similar to a function definition omitting the name.
// function literal def funlit(def(a:'') => a & a) funlit(‘ab’) // function variable var funval := def def(a:'') => a & a funval(‘ab’) // direct call on function value (def def(a:'') => a & a)(‘ab’)
In this example, funlit, funval and the literal lambda can be called interchangeably. The value can be stored in a variable, user type component or tuple attribute and can be passed as a parameter. There is no type declaration as such, just literals of the type. Treating the function call on values as a postfix operator produces an odd asymmetry:
funval(‘ab’) // legal funlit(‘ab’) // legal (funval)(‘ab’) // legal (funlit)(‘ab’) // illegal! [Built in and user-defined operators are still second class.]
Please note that these are individual function values, not methods. They have access to local and global scope, but there is no object or instance scope.
The latest release provides the working system and samples. Download from github.
]]>