Muldis Data Systems
About Muldis D

Muldis D is an industrial-strength computationally complete high-level programming language with fully integrated database functionality; you can use it to define, query, and update ("object") relational databases, as well as write general purpose applications. You can consider it a child of Perl and SQL, but it also borrows from many other languages. The language's paradigm is a mixture of declarative, homoiconic, functional, imperative, and object-oriented. Muldis D is currently under development, and the first working version should be available in the middle of 2014.

See a complete Muldis D example here, consisting of a CD database schema plus test application.

See the end of this page for a complete simple "hello world" program, which interacts with the user.

See https://github.com/muldis/ for the latest Muldis D specification and implementations (some outdated) as it has so far been written.

See http://search.cpan.org/dist/Muldis-D/ for an outdated formal specification rendered in HTML.

For all intents and purposes you can consider Muldis D to be what SQL could have been; it can express anything useful that SQL can, but in a much improved manner. So it should be easier to write more expressive and less kludgy code in Muldis D than in SQL. The following simple code comparisons (and aforementioned code examples) demonstrate this:

Muldis DSQL (parenthesis means as a scalar query)
  'x'
  (SELECT 'x')

  (SELECT 'x' FROM dual)
  tab1
  SELECT DISTINCT * FROM tab1
  #tab1
  (SELECT COUNT(DISTINCT *) FROM tab1)
  tab1^:@{col1,col2}

  tab1 on :@{col1,col2}
  SELECT DISTINCT col1, col2
  FROM tab1
Input: tab(col1,col2,col3,bigcol4,col5,col6,col7,col8)
  tab^-$bigcol4

  tab but $bigcol4
  SELECT DISTINCT col1, col2, col3, col5, col6, col7, col8
  FROM tab
  tab1 matching :@{
    { col1 : 'hello', col2 : 5 },
    { col1 : 'world', col2 : 7 },
  }

  tab1 ⋉ :@{
    { col1 : 'hello', col2 : 5 },
    { col1 : 'world', col2 : 7 },
  }
  SELECT DISTINCT *
  FROM tab1
  WHERE (col1, col2) IN (
    SELECT 'hello' AS col1, 5 AS col2
    UNION
    SELECT 'world' AS col1, 7 AS col2
  )

  SELECT DISTINCT *
  FROM tab1
  WHERE col1 = 'hello' AND col2 = 5
     OR col1 = 'world' AND col2 = 7
Inputs: tab1(col1,col2,col3), tab2(col2,col3,col4)
Result: tab(col1,col2,col3,col4)
  tab1 join tab2

  tab1 ⋈ tab2
  SELECT DISTINCT *
  FROM tab1 NATURAL INNER JOIN tab2

  SELECT DISTINCT *
  FROM tab1 INNER JOIN tab2 USING (col2, col3)

  SELECT DISTINCT tab1.*, tab2.col4
  FROM tab1 INNER JOIN tab2
    ON tab2.col2 = tab1.col2 AND tab2.col3 = tab1.col3
Input: tab1(col1,col2,col3)
  tab1 rename :%{foo : $col1, bar : $col3}
  SELECT DISTINCT col1 AS foo, col2, col3 AS bar
  FROM tab1
Summarize by named attributes giving count:
  count( people, by : :@{age,ctry}, add : $count_by_age_ctry )

  ?#people
    ?? group( people, by : :@{age,ctry}, add : $grouped )
      map :&(.0^:@{age,ctry} add :%{count_by_age_ctry : #.0.grouped})
    !! :@{age,ctry,count_by_age_ctry}
  SELECT age, ctry, COUNT(DISTINCT *) AS count_by_age_ctry
  FROM people
  GROUP BY age, ctry
  ?#sales
    ?? group( sales, by : $sku, add : $grouped )
      map :&(ps ::= .0.grouped^+$price;
        :%{sku : .0.sku, revenue : [+] ps, low : [min] ps, high : [max] ps})
    !! :@{sku,revenue,low,high}
  SELECT sku, SUM(price) AS revenue, MIN(price) AS low, MAX(price) AS high
  FROM sales
  GROUP BY sku
  count := #(foo where
      :&(.0.name = 'John' and (.0.title = 'Mr' or .0.abbrev = 'Dr'))
    );
  SELECT COUNT(DISTINCT *) INTO (count) FROM foo
  WHERE name = 'John' AND ( title = 'Mr' OR abbrev = 'Dr' )
  foo := bar;
  START TRANSACTION;
  SET CONSTRAINTS ALL DEFERRED;
  TRUNCATE foo;
  INSERT INTO foo SELECT DISTINCT * FROM bar;
  SET CONSTRAINTS ALL IMMEDIATE;
  COMMIT;
  foo :=union bar;

  foo :=∪ bar;
  INSERT INTO foo (SELECT DISTINCT * FROM bar EXCEPT SELECT * FROM foo)
  foo :=union :@[col1,col2]:{ [1,3], [2,4] };

  foo :=∪ :@[col1,col2]:{ [1,3], [2,4] };

  r ::= :@[col1,col2]:{ [1,3], [2,4] };
  foo :=union r;
  INSERT INTO foo ( col1, col2 ) VALUES ( 1, 3 ), ( 2, 4 )
  foo :=!matching :@[col1]:{ [10],[20] }

  foo :=⊿ :@[col1]:{ [10],[20] }
  DELETE FROM foo WHERE col1 = 10 OR col1 = 20
  foo^:@{col1,col2} := :%{ col1 : 1, col2 : 6 };
  UPDATE foo SET col1 = 1, col2 = 6

Muldis D provides an effective means to reduce or avoid entirely the problem of "object-relational impedance mismatch". The primary way that Muldis D deals with the above problem is that it natively supports user-defined types at the database level, which can be arbitrarily complex such as to support collection values as attribute values. And so users can effectively use their same type definitions in both the database and in their application, with no non-trivial mapping required. This is in contrast with typical database ORM tools which only remap application objects into simpler built-in database types like plain numbers and strings.

The syntax of Muldis D is like that of a general purpose programming language, with Perl 6 being a strong influence (and multiple other languages contributed too), but it also greatly resembles SQL as well, and so shouldn't be too difficult to learn.

In contrast to a typical SQL DBMS which uses multiple programming languages together, where SQL is used for queries and something else (possibly SQL-alike) for defining stored procedures, a Muldis D DBMS would use the same Muldis D language to assume both roles. Moreover, Muldis D can also be used to write your main application, if you wanted to; in fact, this kind of usage is encouraged for many common cases of simple database-using web applications.

See http://muldis.com/CD.html a simple (outdated) example of actual Muldis D code, which defines both a database schema and some routines which query or update the database. The example is derived from the "simple CD database example" that comes with the manual/cookbook of the DBIx::Class Perl module. The example is essentially complete, with scaffolding, such that you could almost just run it as is through a Muldis D compiler/interpreter and it would run.

Muldis D is intended for at least 2 primary uses; in the long term, it is intended to supplant SQL as the access language of choice for relational DBMS vendors in their products; in the short term, it is intended to be the most effective intermediate representation for code that either is intended to have SQL (or Perl/etc in the case of less capable or privilege-restricted DBMSs, including non-relational or "No-SQL" ones) generated from it, or that SQL would be parsed into it, and so it would be the tool of choice for making widely-DBMS-portable schemas and applications, that leverage a high fraction of a DBMSs native features rather than a lowest common denominator, or for porting such; moreover, it is meant to abstract away where code actually runs, so an implementation can choose to either have parts of it run on a server or on a client (similarly to how tools like LLVM can transparently make code run in the CPU or the GPU, as is best or is even possible at the time). The intermediate representation usage is supported in particular by Muldis D having both regular string forms, for when you manually write code, as well as Perl data structure forms, for when you are generating code, good for preventing "SQL injection attacks"; and being homoiconic, Muldis D can manipulate and run Muldis D code at runtime.

Muldis D is an Acmeist programming language for writing portable database modules, that work with any DBMS and with any other programming language, for superior database interoperability.

Use Cases

While the specification of Muldis D is essentially complete and so can mostly be implemented as is, no Muldis D implementation actually exists yet. A complete reference implementation is expected in the next year or so, but it isn't here yet. Actually, at this point it is expected that the reference implementation (now under development) will lead the spec in the near future, in that it will implement a substantially updated Muldis D syntax and standard library, before the specification then catches up to it. But despite this, Muldis D still can be put to work for you today, in a limited fashion.

Practical benefits that you can have right now include:

  1. Use Muldis D as a more-expressive prototyping language; you can write out your database-concerning code in Muldis D first to get a good feel for your database schema and queries, and then write it out in analogous SQL afterwards when you have your planning worked out.

  2. Makers of SQL-generating toolkits (such as the Perl modules Fey or SQL::Abstract or Rose::DB::Object::QueryBuilder) can use Muldis D's Perl data structure form as a guide to improve their APIs so that their users can then express what they want more easily and cleanly, while gaining SQL's full feature-set without having to write any string-SQL snippets, because Muldis D's coverage is fairly thorough. Similarly, various database access toolkits (such as DBIx::Class or SQL::Translator or DBIx::Perlish) or ORMs can be updated to use the code representation that Muldis D specs as their internal representation, or provide some of that in their API, as they go about their database-mapping duties.

    Also, such toolkits can, when used to deploy or update a generated database schema, also deploy a lot of their now-more-portable query code or data-manipulation code as database stored routines, so that there is less fretting and contrivance at normal application runtime where they try to load user client code lazily or conditionally for performance because it would be expensively regenerated every time; the greater use of database stored procedures would mean that more of the relevant code is pre-compiled and there is less of it in always-regenerated form to fuss about on the client side.

    Note that, while Muldis D should be able to represent and map to/from any database schema, it works best with a well-designed schema. In this way I have made the same decision as some other database tools. If your schema is well-designed, it should map cleanly, make beautiful code, and perform well; if your schema is badly designed, it would map more roughly, with more kludgey looking code, and performance can suffer. Muldis D is designed intentionally such that good practices are given the easiest path to them, and poor practices have a less-desirable path. Focusing on supporting good designs is better for sanity and for keeping the bug count down; trying to make poor designs perform well is like trying to make a program that returns the right results when given the wrong inputs. An example good practice is writing generic queries with bind parameters, which can be pre-compiled, rather than writing a separate data-specific query for each database invocation.

  3. Designers of other programming languages, especially ones still under active design and development such as Perl 6, can look at Muldis D and see if any of its ostensibly novel design aspects or features might influence them as they evolve their own languages. For example, how it actually is practical and useful to have relational types and operators integrated into your language as much as the likes of arrays, sets, and dictionaries/hashes are.

Features of Muldis D

Currently fully-specified features include:

Currently partially-specified features include:

Currently mostly-unspecified but intended features include:

The Next Steps

While essentially complete, the Muldis D language specification is officially considered "pre-alpha" quality, because it is mostly untested in execution. (Except for an (outdated) adapted subset, by the Set::Relation Perl module, which does execute now.)

The next major milestone for Muldis D will be when a working self-contained reference implementation of the language has been made, in the form of a pure-Perl DBMS, Muldis::D::RefEng. At this point, both the language and said implementation will officially be considered "alpha" quality. This is expected around the middle of 2014.

Following that, perhaps the next major milestone will include any of having a decent TAP-based test suite, or having multiple implementations including a Parrot-hosted language, or implementations over SQL DBMSs (starting with PostgreSQL, which seems to be the most conducive environment for it, considering its existing good feature set, solid quality, freedom, and its developers' priorities; other early hosting targets include Sybase, DB2, Oracle, and SQLite, where Perl or another HLL will be used to support stored procedures for the latter), at which point the language can officially be considered "beta" quality. This is expected some time in 2015.

Note that Muldis D is designed such that it can be mostly self-hosted (written in itself) meaning once you have one functional implementation, you have most of another one, and the only reason to be less self-hosted is to aid performance, rather than just getting it to work at all; MREE will be written in that way.

Forums

See also http://mm.darrenduncan.net/mailman/listinfo for the 4 official public email lists that are specific to Muldis D and its implementations: "-announce", "-users", "-language", and "-devel"; just the latter 3 are for discussions.

Muldis D and its official implementations are commercially supported by Muldis Data Systems.

If you want a "homepage" url to link to, you can use http://www.muldis.com/ concerning this project or particularly its commercial support.

And http://github.com/muldis/ is its main public GIT version control repository.

Hello World

Here's a bonus Muldis D example, a complete simple program that first gathers info on people from the user, and then lets the user search that information; it only uses lexical variables.

    Muldis_D:PT_STD:ASCII:"http://muldis.com":0;

    package My_App ::= My_App:"http://mycorp.com":0
    {
        using Muldis_D ::= Muldis_D:"http://muldis.com":0;

        searching [Muldis_D,My_App];

        bootstrap ::= stimulus_response_rule
            when loaded invoke main;
            /* This causes main() to auto-run when this package is loaded. */

    /*************************************************************************/

    main ::= procedure () :
    [
        /* This is the program's main procedure. */
        write_Text_line( 'Hello!' );
        var people : @ := :@{name,addr};
        var name : Text;
        var addr : Text;

        write_Text_line( 'First add some info.  Enter blank line when done.' );
        gather_person ::= loop
        [
            /* Gathers person info from user. */
            prompt_Text_line( &name, 'Enter a person\'s name: ' );
            if name = '' then leave gather_person;
            prompt_Text_line( &addr, 'Enter that person\'s address: ' );
            if addr = '' then leave gather_person;
            /* Adds a person to our db of people. */
            people :=union :%{:name,:addr};
        ];

        write_Text_line( 'Now search for info.  Enter blank line when done.' );
        lookup_person ::= loop
        [
            prompt_Text_line( &name, 'Enter a name to search for: ' );
            if name = '' then leave lookup_person;
            /* Look up person by name, get their address. */
            matched_people ::= people matching :%{:name};
            write_Text_line(
                given #matched_people
                    when 0 then 'No person matched'
                    when 1 then 'Found address for that person is: '
                        ~ (%matched_people).addr
                    default 'More than one person matched'
            );
        ];

        write_Text_line( 'Goodbye!' );
    ];

    /*************************************************************************/

    };
    /* package My_App */

Copyright © 2007-2014, Muldis Data Systems, Inc.
http://www.muldis.com/
MULDIS and MULDIS MULTIVERSE OF DISCOURSE are trademarks of Muldis Data Systems, Inc.

Muldis Data Systems is not affiliated with Muldis Consultants & Engineers (http://www.muldis.nl/), which has its own distinct MULDIS trademark.