[VPM] FYI - modern Muldis D code examples

Darren Duncan darren at darrenduncan.net
Wed Apr 8 01:08:14 PDT 2009


Hello,

If any of you have been confused in regards to understanding my periodically
touted Muldis D language, which is probably nearly everyone, I've got something
here today that may make understanding it a lot easier: some realistic example
code, of which I've got about two dozen examples translated from SQL.

To recap, Muldis D is my new programming language part of whose purpose is to
eventually succeed SQL as the query+DDL language of choice for relational DBMSs,
in the same manner that Perl 6 is intended to eventually supplant Perl 5.   (And
yes, I am being pragmatic and realize it won't happen overnight.)  The design of
Muldis D is like a cross between SQL and Perl 6 and it should be easy to learn.

Muldis D is also intended for use today as a source of design ideas for building
toolkits that interface between a DBMS and an application, particularly ones
that want to go beyond current solutions and offer portable parsing and
generation of SQL stored routines and schemas including such, and Muldis D can
be a primary inspiration for the design of intermediate representations of both
SQL data and code that are widely portable.  Muldis D is rigorous enough that
one could just implement it literally (which I intend to do separately), but you
don't actually have to understand even most of it to glean useful ideas from it
and save yourself from reinventing the wheel in inferior ways.

For context, http://search.cpan.org/dist/Muldis-D/ is where I've published the
rigorous definition of the language, which you can use as a reference later.  I
consider the language spec to be maybe 90% done, and the single largest
remaining thing to rigorously define is its concrete syntax for everything
except value literals, which are done.

So most of this email consists of a number of SQL examples taken from the new
manual for the SQL::Abstract 2.0 Perl module, and for each a translation into
concrete PTMD_Tiny dialect Muldis D code (currently going beyond the scope of
what is rigorously defined).  For the original SQL examples, go to
http://github.com/ashb/sql-abstract/tree/0f93e5f7e64a55f293efd70c2769d1fdbe22da38/lib/SQL/Abstract/Manual 

and look at Examples.pod.

As a simple point of explanation, at the top level all Muldis D code takes the
form of either a value literal or an invocation of a routine or a definition of
a routine.  So if you were using the Perl DBI module to perform a query or run
transient DML code, passed to its prepare() method, what you pass would be
typically a procedure definition, and DBI's bind parameters for the query would
bind to the declared parameters of that procedure, which are typically named
(rather than positional question marks), like some SQL DBMSs spell out :param or
@param etc.  Another detail is that for a routine representing a SELECT, the
result comes back via a subject-to-update/INOUT parameter; it does not come back
out of band, and so is more like how a normal programming language works.

Now this syntax isn't fully complete, but I figure it is maybe 80% like what the
final version would be.

Also let me emphasize that these examples are in canonical character string
form, same as SQL code.  While the Muldis D spec also specifies a version in
terms of Perl structures ala SQL::Abstract et al, that isn't illustrated here;
however that version would closely resemble a concrete syntax tree from parsing
the version below.  The true AST of Muldis D, which is fully (or 95+%) specced,
is its system catalog, analogous to the SQL INFORMATION_SCHEMA but that all code
is decomposed, including routine and view etc definitions.

That said, the canonical Muldis D code intentionally has very simple grammar, so
it should be easy to parse or generate; it should also resemble its lower level
AST form enough that details like named expression nodes and the separation of
pure from impure code shows up often.

-- Darren Duncan

==========

SQL:

     SELECT 1

Muldis D Text:

     function func1 (NNInt <--) {
         main {
             1;
         }
     }

or:

     function func1 (Relation <--) {
         main {
             Relation:{ { attr1 => 1 } };
         }
     }

==========

SQL:

     SELECT NOW() AS time FROM dual AS duality

Muldis D Text:

     procedure proc1 (Instant &$time) {
         main {
             fetch_current_instant( target => $time );
         }
     }

or:

     procedure proc1 (Relation &$r) {
         main {
             var Instant $now;
             fetch_current_instant( target => $now );
             inn.wrapup( &r => $r, now => $now );
         }
         inner_updater wrapup (Relation &$r, Instant $now) {
             $r := Relation:{ { time => $now } };
         }
     }

==========

SQL:

     SELECT 1 FROM foo LEFT OUTER JOIN bar ON ( foo.col1 = bar.col2 )

Muldis D Text:

     # Note: This example assumes neither of foo|bar have col named colJ. #

     procedure proc1 (Relation &$result) {
         main {
             inn.query( &result => $result, db => $fed.data.db1 );
         }
         inner_updater query (Relation &$result, Database $db) {
             $primary := ($db.foo{col1=>colJ});  # aka rename() #
             $secondary := ($db.bar{col2=>colJ});
             $outer_join := outer_join_with_maybes(
                 primary => $primary, secondary => $secondary );
             $null_proj := ($outer_join{});
             $result := static_extension( topic => $null_proj,
                 attrs => Tuple:{ attr1 => 1 } );
         }
     }

or, if we simply want to know if there were matching rows in foo and bar:

     procedure proc1 (Bool &$result) {
         main {
             inn.query( &result => $result, db => $fed.data.db1 );
         }
         inner_updater query (Bool &$result, Database $db) {
             $primary := ($db.foo{col1=>colJ});  # aka rename() #
             $secondary := ($db.bar{col2=>colJ});
             $join := ($primary join $secondary);
             $result := is_not_empty( topic => $join );
         }
     }

==========

SQL:

     SELECT * FROM foo WHERE name = 'John'

Muldis D Text (with the data to match on hard-coded):

     procedure proc1 (Relation &$result) {
         main {
             inn.query( &result => $result, db => $fed.data.db1 );
         }
         inner_updater query (Relation &$result, Database $db) {
             $filter := Relation:{ { name => 'John' } };
             $result := semijoin( source => $db.foo, filter => $filter );
         }
     }

or, with the data to match on in a bind var:

     procedure proc1 (Relation &$result, Text $name) {
         main {
             inn.query( &result => $result,
                 db => $fed.data.db1, name => $name );
         }
         inner_updater query (Relation &$result, Database $db, Text $name) {
             $filter := Relation:{ { name => $name } };
             $result := semijoin( source => $db.foo, filter => $filter );
         }
     }

or:

     procedure proc1 (Relation &$result, Relation $filter) {
         main {
             inn.query( &result => $result, db => $fed.data.db1,
                 filter => $filter );
         }
         inner_updater query
                 (Relation &$result, Database $db, Relation $filter) {
             $result := semijoin( source => $db.foo, filter => $filter );
         }
     }

==========

SQL:

     SELECT COUNT(*) FROM foo
     WHERE name = 'John' AND ( title = 'Mr' OR abbrev = 'Dr' )

Muldis D Text:

     procedure proc1 (NNInt &$count) {
         main {
             inn.query( &count => $count, db => $fed.data.db1 );
         }
         inner_updater query (NNInt &$count, Database $db) {
             $filtered := restriction(
                 topic => $db.foo, func => F->inn.filter );
             $count := cardinality( topic => $filtered );
         }
         inner_function filter (Bool <-- Tuple $topic) {
             (($topic.name === 'John')
                 and (($topic.title === 'Mr') or ($title.abbrev === 'Dr')))
         }
     }

==========

SQL:

     SELECT COUNT(DISTINCT(*)) FROM foo
     WHERE ( name = 'John' AND title = 'Mr' ) OR abbrev = 'Dr'

Muldis D Text:

     # Note: Above DISTINCT not translated as all ops are set-based. #

     procedure proc1 (NNInt &$count) {
         main {
             inn.query( &count => $count, db => $fed.data.db1 );
         }
         inner_updater query (NNInt &$count, Database $db) {
             $filtered := restriction(
                 topic => $db.foo, func => F->inn.filter );
             $count := cardinality( topic => $filtered );
         }
         inner_function filter (Bool <-- Tuple $topic) {
             ((($topic.name === 'John') and ($topic.title === 'Mr'))
                 or ($title.abbrev === 'Dr'))
         }
     }

==========

SQL:

     SELECT foo, bar, baz FROM foo ORDER BY bar, baz DESC GROUP BY 1,3,2

Muldis D Text:

     # Note: Above GROUP BY not translated as it's a no-op. #

     procedure proc1 (Array &$result) {
         main {
             inn.query( &result => $result, db => $fed.data.db1 );
         }
         inner_updater query (Array &$result, Database $db) {
             $proj := ($db.foo{foo,bar,baz});  # aka projection() #
             $result := QArray_from_wrap(
                 topic => $proj, ord_func => F->inn.qsort );
         }
         inner_function qsort (Order <-- Tuple $topic, Tuple $other) {
             $e1 := ($topic.bar <=> $other.bar);
             $e2 := ($other.baz <=> $topic.baz);
             $e3 := Array:[ $e1, $e2 ];
             $'' := Order_reduction( topic => $e3 );
         }
     }

==========

SQL:

     SELECT * FROM ( SELECT 1 ) AS foo

Muldis D Text:

     function func1 (Relation <--) {
         main {
             $foo := Relation:{ { attr1 => 1 } };
             $'' := $foo;
         }
     }

==========

SQL:

     INSERT INTO foo ( col1, col2 ) VALUES ( 1, 3 )

Muldis D Text (with data to insert hard-coded):

     procedure proc1 () {
         main {
             var Tuple $t;
             inn.init_t( &t => $t );
             assign_insertion( &r => $fed.data.db1.foo, t => $t );
         }
         inner_updater init_t (Tuple &$t) {
             $t := Tuple:{ col1 => 1, col2 => 2 };
         }
     }

or, with the data to insert as a bind var:

     procedure proc1 (type.tuple_from.fed.data.db1.foo $t) {
         main {
             assign_insertion( &r => $fed.data.db1.foo, t => $t );
         }
     }

==========

SQL:

     INSERT INTO foo ( col1, col2 ) VALUES ( 1, 3 ), ( 2, 4 )

Muldis D Text (with data to insert hard-coded):

     procedure proc1 () {
         main {
             var Relation $r;
             inn.init_r( &r => $r );
             assign_union( &topic => $fed.data.db1.foo, other => $r );
         }
         inner_updater init_r (Relation &$r) {
             $r := Relation:[col1,col2];{ [1,3], [2,4] };
         }
     }

or, with the data to insert as a bind var:

     procedure proc1 (type.fed.data.db1.foo $r) {
         main {
             assign_union( &topic => $fed.data.db1.foo, other => $r );
         }
     }

==========

SQL:

     UPDATE foo SET col1 = 1

Muldis D Text (with data to substitute hard-coded):

     procedure proc1 () {
         main {
             var Tuple $attrs;
             inn.init_attrs( &attrs => $attrs );
             assign_static_substitution(
                 &topic => $fed.data.db1.foo, attrs => $attrs );
         }
         inner_updater init_attrs (Tuple &$attrs) {
             $attrs := Tuple:{ col1 => 1 };
         }
     }

or, with the data to substitute as a bind var:

     procedure proc1 (Tuple $attrs) {
         main {
             assign_static_substitution(
                 &topic => $fed.data.db1.foo, attrs => $attrs );
         }
     }

or:

     procedure proc1 (Int $col1) {
         main {
             var Tuple $attrs;
             inn.init_attrs( &attrs => $attrs, col1 => $col1 );
             assign_static_substitution(
                 &topic => $fed.data.db1.foo, attrs => $attrs );
         }
         inner_updater init_attrs (Tuple &$attrs, Int $col1) {
             $attrs := Tuple:{ col1 => $col1 };
         }
     }

==========

SQL:

     UPDATE foo SET col1 = 1, col2 = 6

Muldis D Text (with data to substitute hard-coded):

     procedure proc1 () {
         main {
             var Tuple $attrs;
             inn.init_attrs( &attrs => $attrs );
             assign_static_substitution(
                 &topic => $fed.data.db1.foo, attrs => $attrs );
         }
         inner_updater init_attrs (Tuple &$attrs) {
             $attrs := Tuple:{ col1 => 1, col2 => 6 };
         }
     }

or, with the data to substitute as a bind var:

     procedure proc1 (Tuple $attrs) {
         main {
             assign_static_substitution(
                 &topic => $fed.data.db1.foo, attrs => $attrs );
         }
     }

or:

     procedure proc1 (Int $col1, Int $col2) {
         main {
             var Tuple $attrs;
             inn.init_attrs( &attrs => $attrs,
                 col1 => $col1, col2 => $col2 );
             assign_static_substitution(
                 &topic => $fed.data.db1.foo, attrs => $attrs );
         }
         inner_updater init_attrs (Tuple &$attrs, Int $col1, Int $col2) {
             $attrs := Tuple:{ col1 => $col1, col2 => $col2 };
         }
     }

==========

SQL:

     DELETE FROM foo WHERE col1 = 10

Muldis D Text (with data to filter by hard-coded):

     procedure proc1 () {
         main {
             var Relation $filter;
             inn.init_filter( &filter => $filter );
             assign_semidifference(
                 &source => $fed.data.db1.foo, filter => $filter );
         }
         inner_updater init_filter (Relation &$filter) {
             $filter := Relation:{ { col1 => 10 } };
         }
     }

or, with the data to filter by as a bind var:

     procedure proc1 (Relation $filter) {
         main {
             assign_semidifference(
                 &source => $fed.data.db1.foo, filter => $filter );
         }
     }

or:

     procedure proc1 (Int $col1) {
         main {
             var Relation $filter;
             inn.init_filter( &filter => $filter, col1 => $col1 );
             assign_semidifference(
                 &source => $fed.data.db1.foo, filter => $filter );
         }
         inner_updater init_filter (Relation &$filter, Int $col1) {
             $filter := Relation:{ { col1 => $col1 } };
         }
     }

==========

SQL:

     INSERT INTO foo ( col1, col2 ) SELECT col1, col2 FROM bar;

Muldis D Text (if bar has more than 2 attributes):

     procedure proc1 () {
         main {
             var Relation $bar_proj;
             inn.projcol12( &bar_proj => $bar_proj,
                 bar => $fed.data.db1.bar );
             assign_union( &topic => $fed.data.db1.foo,
                 other => $bar_proj );
         }
         inner_updater projcol12 (Relation &$bar_proj, Relation $bar) {
             $bar_proj := ($bar{col1,col2});
         }
     }

or, if bar has only the 2 attributes:

     procedure proc1 () {
         main {
             assign_union( &topic => $fed.data.db1.foo,
                 other => $fed.data.db1.bar );
         }
     }

==========



More information about the Victoria-pm mailing list