The Intended Role of Andl

The intended role of Andl is to be the implementation language for the data model of an application. That needs some explanation.

Andl does what SQL does, but it is not SQL. It is possible to code the business model of an application in SQL dialects such as PL/PSM (SQL Standard), PL/SQL (Oracle) or Transact-SQL (Microsoft) but SQL has many problems (see here) and few people make this choice. Andl aims to provide a language free of these problems.

Andl has been designed to be used as follows. First, an application developer takes a set of business requirements that comprise a data model, a set of operations on that model, and a set of requirements for users to interact with the model. The data model is implemented in the form of tables in a relational database management system, and the user interface and related business requirements is implemented using some chosen front end technology: web, desktop or mobile as needed. Andl is used to implement the operations on the data model, and the means by which the user interface queries and updates the data in the model.

The effect is that the ‘object relational mismatch’ disappears, and there is no longer any need for an ‘object relational mapper’. No objects are needed between the data model and the user interface, just data as individual items, rows or tables.

There is still a role for a general purpose programming language like C# or Java, acting as glue to join various parts together and also in implementing various parts of the type system that Andl depends on. There is still a role for a front end technology based on languages such as HTML and JavaScript since Andl does not offer a user interface.

But for the heart of the business application, for operations on the data and for implementing the rules, there is Andl.

Leave a Comment

Filed under Rationale

What exactly is so bad about SQL?

That’s the question I’ve been asked: What exactly is so bad about SQL?. It’s not easy to produce a satisfying answer.

Most of the SQL that most people use is roughly SQL-1992, with perhaps a few bits from SQL_2003 like WITH. That’s a data sub-language, and while you can only write a small part of an application in this language, most of the critical logic has to be written in a general purpose object-oriented programming language. That’s where the ‘object relational impedance mismatch’ comes from.

Here is a list of the ‘Fatal Flaws’ of this kind of SQL, taken from the Askew Wall by Hugh Darwen.

  • Anonymous columns (partly addressed in 1992)
  • FROM clause restricted to named tables (fixed in 1992)
  • Duplicate column names
  • Order of columns is significant
  • Duplicate rows
  • NULL
  • Failure to support degenerate cases (e.g. columnless tables)
  • Failure to support “=“ properly
  • and lots more, and probably to come.

Chris Date and Hugh Darwen wrote a book called Database, Types and the Relational Model. It contains the rational for the Third Manifesto, and also an assessment of how well SQL stacks up against the Manifesto. The version of SQL targeted is SQL:2003 (with PL/PSM). The book does an extensive analysis of this version of SQL against TTM, and identifies sins of omission and commission. Obviously SQL inherits many legacy features and the consequences of decisions made years ago, however many of the gaps appear to have been filled in. Rather than focussing on the bad things one can do in SQL, my question comes down to these.

  1. If you set out to code in a TTM compliant style, how close can you get in SQL:2003?
  2. If a TTM language generated SQL:2003 as a backend target, how close could it get to TTM compliance?

Obviously in either case:

  1. NULL should never be used (and some extra code is needed to avoid it arising during evaluations, eg in aggregates)
  2. Some comparisons need to be coded carefully to avoid mistakes (eg trailing spaces in CHAR)
  3. All queries should have DISTINCT in effect to avoid duplicate rows
  4. Column attributes should be unique and in the same order in all tables (that have the same columns)
  5. Keys should be defined as compliant candidate keys.
  6. Tuple level operations should be avoided (FETCH, WHERE CURRENT)
  7. Much care should be taken to use certain syntactic features and not use others.

The only failures (from that list) that I can find are:

  1. There are severe limitations in the type system (possreps, components, selectors, constraints, tuple types, relation types).
  2. No assignment (or multiple assignment) for relation types; emulation via DELETE/INSERT runs into the multiple assignment problem.
  3. No relation-valued attributes (but might be emulated using MULTISET)
  4. No public relvars (can only share database tables)
  5. No nested transactions; some other issues around transactions.

I should point out here that this only applies directly to SQL:2003, but in broad terms it probably applies to later versions of the standard and implementations that claim substantial compliance with the standard (perhaps with different syntax).

So apart from the type system, the horrible syntax and the plethora of ways to shoot yourself in the foot, SQL:2003 is not all that bad! My answer to my two questions is:

  1. Quite close, apart from the seriously deficient type system.
  2. Very close indeed, if the compiler can provide its own type system on top of that provided by SQL, its own syntax, and then generate bog standard SQL (or some dialect to suit a particular engine).

You can write good code in SQL:2003 or later, but it’s not easy. A better language sitting on top of SQL might make it a whole lot easier. Is that where Andl fits?

Leave a Comment

Filed under Backend, Rationale

Could SQLite be the engine for Andl?

I have been looking into using SQLite as the ‘backend’ for Andl. It has:

  • Most of ‘standard SQL’
    • CREATE, ALTER, DROP TABLE
    • TRANSACTIONS
    • SELECT, INSERT, UPDATE, DELETE
    • INDEXES, VIEWS, TRIGGERS
  • Minimal types: null, integer, real, text, blob
  • Parameters for prepared statements
  • Minimal functions, including aggregates
    • Minimal (open) expressions
    • User-defined functions in C

It does not have:

  • A ‘real’ type system, including relational types
  • Stored code: statements, functions, etc
  • Session variables

The results of a reasonably detailed examination of this 155,000 lines of dense C code:

  • Hand-coded lexer
  • LALR parser
  • No real symbol table, just the schema, some fixed tables and the parse tree.
  • Builds an AST-like tree structure called Parse.
  • Progressively converts that into a VM opcode tree called Vdbe.
  • The VM tree is manipulated directly by the query planner based on stats (if available) and execution time estimates.
  • Relational operators (JOIN, etc) are implemented as nested loops in VM opcodes (there is no pipe).
  • A ‘prepare’ operation returns a prepared statement which is simply the VM tree.
  • An ‘execute’ operation binds variable values.
  • A ‘step’ operation runs the VM program until a row is available.

[If you want to see the VM opcodes and the join algorithms, just use “EXPLAIN query”.]

My problem is that the Parse structure is tightly bound to the SQL language, and the Vdbe level sits after the query planner. There is no obvious layer at which it’s possible to create VM programs independent of SQL but still taking advantage of JOIN and other algorithms.

This does not look like a good prospect. I shall keep looking.

Leave a Comment

Filed under Backend, Pondering

A Paraphrase of the Third Manifesto

The Third Manifesto is the highly authoritative result of over two decades of work by CJ Date and Hugh Darwen. It sets out their view on the future for database management systems, and on a language in particular. To a large extent Andl is based on that work.

But TTM (as it is known) is not an easy read. It is written in academic language for an academic audience, and not everyone will find that accessible. This TTM Paraphrase is my attempt to express the ideas of TTM in language more familiar and more accessible to the general IT reader. It is the document I use when I want to quickly remind myself of some key points and terminology. It’s my ‘cheat sheet’ for TTM.

So I’m publishing this draft in the hope that it will be useful, and to solicit feedback. It is not a substitute for, an improvement on or even a refinement of TTM. At best it may help some people to understand TTM. I know writing it has helped me.

The TTM paraphrase, initial draft release, with minor updates: TTM-para-d26.

Leave a Comment

Filed under TTM

Progress report

Andl has reached the six month mark. I started working on it in October 2014 and the first check in of some actual code was on the 27th. Now it’s April 2015, so where am I up to?

First, how big is it? Basic statistics: Andl is currently about 7500 lines of C# code, 1500 lines of Andl code and other bits and pieces. I’ve probably written twice that to get it to here.

What can it do? A lot. Most of the things that most people write in SQL can easily be handled by Andl code. In my opinion it’s a nice little language of a rather functional kind, and I’ve been surprised at how powerful the relational paradigm is, even when it’s the only collection type and the problem doesn’t look like data.

It implements large chunks of The Third Manifesto, but definitely not all.

  • The type system is reasonably complete, but there are no type constraints and no subtyping. There are components, selectors and getters, but not alternative ‘possreps’.
  • Support for the Relational Algebra and a host of related capabilities is complete for relations. RA support for tuples is indirect, requiring them to be wrapped in a relation first.
  • Relvars can be persisted, but there are no relvar constraints, no real concept of a database and no transactions.

The net effect is that it implements the TTM requirements for a D language, but excluding RM Prescriptions 14, 15, 16, 17, 23, 24 and 25, and OO Prescriptions 4 and 5. It’s a work in progress.

I have written two scripts of sample code to show off most of what Andl can do.

Leave a Comment

Filed under Backend, Rationale

Sample Code 2

This is a page of sample code, showing basic relational capabilities. It will be part of the download, when available.

// Andl samples -- relational
// Aim is to show an example of every feature
// Also useful as a quick smoke test

// ===== Relational types =====

// Tuples - not used much
{}
{name := 'Smith', age := 17}
{age := 17, name := 'Smith'}

// Relations - used heavily
{{:}}       // empty relation, no attributes, no tuples
{{:}{}}     // ditto, one tuple
{{}}        // same, with type derived by inference
{{},{},{},{},{},{},{},{}}        // exactly the same value (duplicates discarded)

// all the same -- order does not matter
{{name := 'Smith', age := 17}}
{{name:'',age:0}{'Smith', 17}}
{{name:text,age:number}{'Smith', 17}}

// all the same -- differences in order and syntax
{{name := 'Smith', age := 17},{name := 'Jones', age := 35},{age :=199,name:='Frankenstein' }}
{{name := 'Smith', age := 17},{age :=199,name:='Frankenstein' },{age := 35, name := 'Jones'}}
{{name:,age:0}{'Smith', 17}{'Jones', 35}{'Frankenstein',199 }}

// Built in functions
sequence(5)     // relation of N integers {{ N:number }}

r1 := {{name:,age:0}{'Smith', 17}{'Jones', 35}{'Frankenstein',199 }}
r1.schema       // relation of attributes
r1.count        // cardinality
r1.degree       // degree

//===== Basic operations =====

// Load some data from CSV files
S := source('csv:', 'S.csv')
P := source('csv:', 'P.csv')
SP := source('csv:', 'SP.csv')
S
S.schema
S.count
S.degree

//==== monadics =====
// all monadic operations are inside [], in a fixed sequence ?$%{}

// restriction - remove rows
S [ ?(CITY = 'Paris') ]
S [ ?(STATUS > 15 or CITY = 'London') ]

// --- rename - change column names
// rename all
S [ { F1 := S#, F2 := SNAME, F3 := STATUS, F4 := CITY }]
// rename some, the * means keep the rest unchanged
S [ { * F1 := SNAME }]

// --- projection - remove columns
// name all to be kept
S [ { S#, SNAME, CITY }]
// now * means keep all but the ones named
S [ { * STATUS }]

// --- extension - add new columns
// Here * means keep all, add new ones
S [ { * Initial := left(SNAME, 1) }]

// --- combine all three
S [ { CITY, F := STATUS, Initial := left(SNAME, 1) }]
S [ { * SNAME, Initial := left(SNAME, 1) }]

// --- aggregated projection - projection with totalling
S [ { CITY, 
    total := fold(+,STATUS), 
    average := fold(+,STATUS)/fold(+,1) 
} ]

// Note: fold() is only allowed in projection, but looks nicer in a function
sum(n:0) => fold(+,n)
ave(n:0) => fold(+,n)/fold(+,1)
S [ { CITY, total := sum(STATUS), average := ave(STATUS) } ]

// --- ordered extension - means extension with access to other rows
// ordered on CITY but no grouping, so all in one group
S [ $(CITY) { *  
    ord:=ord(),     // unique index based on input, not output
    ordg:=ordg(),   // ord value for first member of group
    lag:=lag(STATUS,1),     // previous value in group, or default
    lead:=lead(STATUS,1),   // next value in group, or default
    nth:=nth(STATUS,1),     // nth value in group, or default
} ]
// ordered and grouped on CITY
S [ $(%CITY) { *  
    ord:=ord(),
    ordg:=ordg(),
    lag:=lag(STATUS,1), 
    lead:=lead(STATUS,1), 
    nth:=nth(STATUS,1), 
} ]
// ordered and grouped on CITY descending, with subtotalling/running sum
S [ $(%-CITY) { *  
    ord:=ord(),
    ordg:=ordg(),
    lag:=lag(STATUS,1), 
    lead:=lead(STATUS,1), 
    nth:=nth(STATUS,1), 
    sum:=fold(+,STATUS),    // running sum within group
    ave:=fold(+,STATUS)/fold(+,1),
} ]

// Ordered used just for display sort
P[$(WEIGHT)]
P[$(COLOR,-WEIGHT)]     // descending

// --- lift anonymous value out of singleton relation
S [ { sum(STATUS) } ]
S [ { ave(STATUS) } ]

//--- nested relation
nr1 := {{ name := 'S', contents := S }}
nr1
nr2 := {
    { name := 'S1', contents := S [?( CITY = 'London')] },
    { name := 'S2', contents := S [?( CITY = 'Paris')] },
    { name := 'S2', contents := S [?( CITY = 'Athens')] } }
nr2
// retrieve one row as relation
nr2 [?(name='S1') { contents }]
// put the relation back together again using fold and union
nr2 [ { fold(union,contents) } ]

//==== dyadics =====

// prepare some subsets
S3 := S [?( S# = 'S3')]    // one single supplier S3
SX := S [?( S# <> 'S3')]   // all suppliers except S3 to make this work better
SY := S [?( S# <> 'S1')]   // all suppliers except S1

// set membership -- all true
S3 sub S        // subset
S sup SX        // superset
S3 sep SX       // separate

// joins

S join SP       // natural join preserves all columns for matching tuples
S compose SP    // projects onto non-common attributes
S semijoin SP   // projects onto left and common attributes
S divide SP     // projects onto left only attributes
S rsemijoin SP  // projects onto right and common attributes
S rdivide SP    // projects onto right only attributes

// antijoins

SX ajoin SP      // antijoin preserves all left attributes for non-matching
SX ajoinl SP     // projects onto left only attributes
SX rajoin SP     // reverse antijoin has right and common attributes
SX rajoinr SP    // projects onto right only attributes

// set operations

SX union SY      // combines all tuples
SX intersect SY  // keep common tuples
SX symdiff SY    // keep non-common tuples
SX minus SY      // keep left minus right
SX rminus SY     // keep right minus left

// all set operations project onto common attributes
SZ := {{ S#:='S99', STATUS:= 999, CITY:='Paris' }}
S union SZ
S minus SZ

// ===== Advanced Usage =====

// --- Nest: replace each tuple of S by one converted into a singleton relation
// {{*}} means 'the current tuple as a singleton relation'
ES1 := S [{ embed := {{*}} }]
ES1

// --- Unnest: using fold union and lift -- advanced usage!
ES1 [{ fold(union,embed) }]

// --- Image relation -- extend S with a partion of SP, removing common fields
// note that S5 gets a partition that is an empty relation
ES2 := S [{ * partition := ( {{*}} rdivide SP) }]
ES2

// Report total no of parts and qty (including S5 which supplies no parts)
ES2 [{ S#, parts:=partition.count, qtys:=partition[{sum(QTY)}] }]

// --- Transitive closure
// MM has tuples reprenting part/subpart assemblies
MM := source('csv:','MM.csv')
MM

// define a relational type
xyt := {{x:='',y:=''}}
// define a recursive function that takes a relation of that type as an argument
tranclo:xyt(xy:xyt) => do {
        ttt := xy[{*z := y}] compose xy[{*z := x}] union xy
        if(ttt = xy, ttt, tranclo(ttt))
    }
// call it with MM as argument, renaming attributes to match
tranclo(MM[ {x:=MAJOR_P#, y:= MINOR_P# } ]) [{MAJOR_P#:=x, MINOR_P#:=y }]

// ===== Updates =====

// Define the 3 updates

// Insert: argument is relation of same heading
up1 => S := union {{ S#:='S9', SNAME:='Moriarty', STATUS:=99, CITY:='Timbuktu' }}
// Delete: read as replace matching rows by nothing
up2 => S := [ ?(S#='S3') ]
// Update: make changes to matching rows
up3 => S := [ ?(S#='S4') { *STATUS:= -10 } ]
// Now perform each update in turn
S // original
up1
S // add S9
up2
S // delete S3
up3
S // update STATUS of S4

// Persistence
// any relvar starting with ^ is persisted

^S := S

// end

Leave a Comment

Filed under Code sample

Sample Code 1

This is a page of sample code, showing basic non-relational capabilities. It will be part of the download, when available.

// Andl samples -- scalar
// Aim is to show an example of every feature
// Also useful as a quick smoke test

//=== Tokens ===
// A free expression is evaluated immediately
'hello world!'
// Single and double quoted strings are concatenated immediately
"Hel""lo" ' World' "!"
// d-string is decimal Unicode char
"Hello" & d'32 87' & "orld"
// h-string is hex Unicode char
"Hello" & h'20 57' & "orld"
// t-string is a Time
t'2015/12/31'
// i-string is an identifier
i'x( )' := 'hello world!'
i'x(' h'20' ')'

//=== Expressions ===
// Logical: all true
true = not false
3 <> 4
'ABC' > 'abc'
t'2015/12/31' < t'01/01/2016'
'Z' >= 'A' and (3<=4 or (true xor false))

// Numeric: all 42
5-8*6/4+7**2
(58 div 8) * (86 mod 8)
(9 or 7) and (9 xor 3) xor 32

// String: mostly Hello World!
'Hello' & " " & 'World!'
trim('Hello ') & ' ' & trim(' World') & trim('   !   ')
fill(' ',10) & fill('Hello Planet???', 5) & fill(' World!', 35)
before(after("@@@>>>Hello World!<<<@@@", '>>>'), '<<<')
toupper('h') & tolower('ELLO') & toupper(' w') & tolower('ORLD!')
// String: other
'hello'.length = length('world')
"Interpolate date: " & t'2015/12/31' & " number:" & 12345 & " logical:" & (2=2)

// Date: 
d1 := dateymd(2015,1,31)
"Date: " & d1 & " Year:" & d1.year & " month:" & d1.month & " day:" & d1.day & " dow:" & d1.dow

// Special: if(), only evaluates one of its arguments
if(true,'Hello World!', "goodbye!")
if(2>2,1/0, 7*6)

//=== Statements ===
// Assignment -- evaluated once
v1 := 'Hello World!'
v1
// Assignment to out sends direct to output
out := v1
// Deferred assignment -- evaluated every time
v2 => v1
v2

// do block creates a local scope and returns value of last expression (statement returns void)
do {
  v2 := v1
  v2
}
// Deferred evaluation wiht do block
v3 => do {
  v2 := v1
  v2
}
v3
// Deferred evaluation with do block and arguments
v4(a) => do {
  v2 := a
  v2
}
// Arguments with literal types (default is text)
v5(a:'',b:0,c:false,d:d1) => do {
  a & b & c & d
}
v5(v1,42,true,dateymd(2015,1,1))
// Arguments with named types
v6(a:text,b:number,c:bool,d:date) => do {
  a & b & c & d
}
v6(v1,42,true,dateymd(2015,1,1))
// Recursion, function name must be typed
fact:0(n:0) => if(n<=1,1,n*fact(n-1))
fact(20)

// ===== Types =====

u1 := ut1 { n:=42, t:=v1, d:= d1 }
"n:" & u1.n & " t:" & u1.t & " d:" & u1.d
u2 := ut1 { n:=41, t:="!"&v1, d:= dateymd(d1.year+1,d1.month,d1.day) }
"n:" & u2.n & " t:" & u2.t & " d:" & u2.d
// Comparison left-to-right
u1 > u2
// Deferred function
f7(u:ut1) => do {
    "n:" & u.n & " t:" & u.t & " d:" & u.d
}
f7(u1)
f7(u2)

// done

Leave a Comment

Filed under Code sample

The Artist-CD-Track sample – version 2

This is an updated version of the Artist-CD-Track sample previously posted.

  1. The syntax for defining a relvar has changed, so that only the heading need be defined as attribute names and types. The type can be inferred from a literal or variable of that type.
  2. The output format has been improved. Note the use of fold(&,) to format lines of output text.
  3. The code has been annotated to assist in understanding.
// translation of sample application from http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class/Manual/Example.pod
#noisy 0

// create the database relvars
artist  := {{ artistid:0, name:'' }}
cd      := {{ cdid:0, artistid:0, title:'', year:0 }}
track   := {{ trackid:0, cdid:0, title:'' }}

// some data in temporary relvars, with auto-generated ordinals
artist_data := {
  { name := 'Michael Jackson'}, 
  { name := 'Eminem' }
} [{ *artistid := ord() }]

cd_data := {
  { title := 'Thriller', name := 'Michael Jackson' },
  { title := 'Bad', name := 'Michael Jackson' },
  { title := 'The Marshall Mathers LP', name := 'Eminem' }
} [{ *cdid := ord() }]

track_data := {
   { title := 'Beat It'         , cd := 'Thriller' },
   { title := 'Billie Jean'     , cd := 'Thriller' },
   { title := 'Dirty Diana'     , cd := 'Bad' },
   { title := 'Smooth Criminal' , cd := 'Bad' },
   { title := 'Leave Me Alone'  , cd := 'Bad' },
   { title := 'Stan'            , cd := 'The Marshall Mathers LP' },
   { title := 'The Way I Am'    , cd := 'The Marshall Mathers LP' }
 } [{ *trackid := ord() }]

 // update the database relvars
artist := union artist_data
cd := union (cd_data join artist)[{ title, cdid, artistid,year:=0}]
track := union (track_data join cd[{ *cd := title }]) [{ trackid, title, cdid }]

// functions to answer various queries
get_tracks_by_cd(t) => cd[ title = t {*title} ] join track
get_tracks_by_artist(a) => (artist[ name = a {*name}] join cd) [{cdid}] join track
get_cd_by_track(t) => track [ title = t {cdid} ] join cd
get_cds_by_artist(a) => artist [name = a {artistid} ] join cd
get_artist_by_track(t) => (track [title = t { cdid }] join cd) [{artistid}] join artist
get_artist_by_cd(t) => (cd [title = t { cdid }] join cd) [{artistid}] join artist

// first show the raw data

crlf := h'd a'
out := crlf & "=== Sample data ===" & crlf
out := artist
out := crlf
out := cd
out := crlf
out := track

// now do the queries

show(title, data:{{str:''}}) => do {
    out := title & crlf & data[ {fold(&, "  " & str & crlf)} ]
}

out := crlf & "=== Query results ===" & crlf
show("Track title:", get_tracks_by_cd('Bad') [ {str:=title} ])
show("Track title:", get_tracks_by_artist('Michael Jackson') [ {str:=title} ])
show("CD title:", get_cd_by_track('Stan') [ {str:=title} ])
show("CD title:", get_cds_by_artist('Michael Jackson') [ {str:=title} ])
show("Artist:", get_artist_by_track('Dirty Diana') [ {str:=name} ])
show("Artist:", get_artist_by_cd('The Marshall Mathers LP') [ {str:=name} ])

The output now looks like this.

 == Sample data ===

artistid | name
--------------------------
       0 | Michael Jackson
       1 | Eminem


cdid     | artistid | title                   | year
--------------------------------------------------------
       0 |        0 | Thriller                |        0
       1 |        0 | Bad                     |        0
       2 |        1 | The Marshall Mathers LP |        0


trackid  | cdid     | title
-------------------------------------
       0 |        0 | Beat It
       1 |        0 | Billie Jean
       2 |        1 | Dirty Diana
       3 |        1 | Smooth Criminal
       4 |        1 | Leave Me Alone
       5 |        2 | Stan
       6 |        2 | The Way I Am

=== Query results ===

Track title:
  Dirty Diana
  Smooth Criminal
  Leave Me Alone

Track title:
  Beat It
  Billie Jean
  Dirty Diana
  Smooth Criminal
  Leave Me Alone

CD title:
  The Marshall Mathers LP

CD title:
  Thriller
  Bad

Artist:
  Michael Jackson

Artist:
  Eminem

Leave a Comment

Filed under Code sample

The Artist-CD-Track sample

The original Artist-CD-Track sample can be found here:  http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class/Manual/Example.pod.

The Muldis-D translation can be found here: http://muldis.com/CD.html.

The Andl implementation is below. Note that they are not strictly comparable because:

  1. Andl provides no persistence. The results are only in memory.
  2. Andl provides limited output formatting.
artist  := {{ artistid:=0, name:='' }} [false]
cd      := {{ cdid:=0, artistid:=0, title:='', year:=0 }} [false]
track   := {{ trackid:=0, cdid:= 0, title:='' }} [false]

artist_data := {
  { name := 'Michael Jackson'}, 
  { name := 'Eminem' }
} [{ *artistid := ord() }]
artist := artist union artist_data

cd_data := {
  { title := 'Thriller',                name := 'Michael Jackson' },
  { title := 'Bad',                     name := 'Michael Jackson' },
  { title := 'The Marshall Mathers LP', name := 'Eminem' }
} [{ *cdid := ord() }]
cd := cd union (cd_data join artist)[{ title, cdid, artistid, year:=0}]

track_data := {
   { title := 'Beat It'         , cd := 'Thriller' },
   { title := 'Billie Jean'     , cd := 'Thriller' },
   { title := 'Dirty Diana'     , cd := 'Bad' },
   { title := 'Smooth Criminal' , cd := 'Bad' },
   { title := 'Leave Me Alone'  , cd := 'Bad' },
   { title := 'Stan'            , cd := 'The Marshall Mathers LP' },
   { title := 'The Way I Am'    , cd := 'The Marshall Mathers LP' }
 } [{ *trackid := ord() }]
track := track union (track_data join cd[{ *cd := title }]) [{ trackid, title, cdid }]

get_tracks_by_cd(t) => cd[ title = t {*title} ] join track
get_tracks_by_artist(a) => (artist[ name = a {*name}] join cd) [{cdid}] join track
get_cd_by_track(t) => track [ title = t {cdid} ] join cd
get_cds_by_artist(a) => artist [name = a {artistid} ] join cd
get_artist_by_track(t) => (track [title = t { cdid }] join cd) [{artistid}] join artist
get_artist_by_cd(t) => (cd [title = t { cdid }] join cd) [{artistid}] join artist

get_tracks_by_cd('Bad') [{ i'Track title' := title }]
get_tracks_by_artist('Michael Jackson') [{ i'Track title' := title }]
get_cd_by_track('Stan') [{ i'CD title' := title }]
get_cds_by_artist('Michael Jackson') [{ i'CD title' := title }]
get_artist_by_track('Dirty Diana') [{ i'Artist name' := name }]
get_artist_by_cd('The Marshall Mathers LP') [{ i'Artist name' := name }]

The output looks like this.

Track title
---------------
Dirty Diana
Smooth Criminal
Leave Me Alone

Track title
---------------
Beat It
Billie Jean
Dirty Diana
Smooth Criminal
Leave Me Alone

CD title
-----------------------
The Marshall Mathers LP

CD title
----------
Thriller
Bad

Artist name
---------------
Michael Jackson

Artist name
-----------
Eminem

Leave a Comment

Filed under Code sample

Transitive closure

I figured out how to write the code for Transitive Closure. Here it is in Andl.

// define recursive function
XYT := {{X:='',Y:=''}}
TRANCLO:XYT(XY:XYT) => do {
  TTT := XY[{*Z := Y}] compose XY[{*Z := X}] union XY if(TTT = XY, TTT, TRANCLO(TTT))
}
// call it
TRANCLO(MM[ {X:=MAJOR_P#, Y:= MINOR_P# } ]) [{MAJOR_P#:=X, MINOR_P#:=Y }]

Note

  1. XYT is an expression which provides the type information for what amounts to a function call. This is structural typing, there are no type names.
  2. The symbol ‘=>’ means deferred evaluation, in this case with arguments. A function call by any other name.
  3. The do {} block allows an arbitrary number of statements, and returns the value of the last expression.

I have to confess I’m quite pleased with the outcome.  Here is the equivalent code in Tutorial-D.

/* the function */
OPERATOR TRANCLO ( XY RELATION { X P#, Y P# } ) RETURNS RELATION { X P#, Y P# } ;
    RETURN 
  ( WITH ( XY UNION ( ( XY RENAME ( Y AS Z ) ) 
              COMPOSE ( XY RENAME ( X AS Z ) ) ) ) AS TTT :
    IF TTT = XY THEN TTT /* unwind recursion */
      ELSE TRANCLO ( TTT ) /* recursive invocation */
    END IF 
  ) ;
END OPERATOR ;
/* the call */
( TRANCLO ( MM RENAME ( MAJOR_P# AS X , MINOR_P# AS Y ) ) ) RENAME ( X AS MAJOR_P# , Y AS MINOR_P# )

The similarities are reasonably obvious.

Leave a Comment

Filed under Code sample