Introduction to Mondrian

Mondrian is a non-strict functional language specifically designed for an OO envrionment. It can be thought of as a “light” version of Haskell with the syntax a meld of that of Haskell and the Java/C family. This page introduces Mondrian, a paper or two also available on the Mondrian website. One day there may be a real reference manual...

Mondrian was designed to be minimal, it is also still evolving, these two explain some of the “quirks” below.

Compilation Units

The unit of compilation is a package, which can be anonymous or named. A Mondrian package maps to a JVM package or .NET namespace. A program can consist of one of more packages. Program execution starts by invoking the main function.

compilationUnit = [packageDeclaration] declarations

packageDeclaration = "package" qualifiedName ";"

Declarations

There are four kinds of declarations: import, class, signature (of a variable) and variable. There is currently no export declaration or any way to limit exports, everything declared is public.

declarations = ( declaration ";" )*
    
declaration =
      importDeclaration
    | classDeclaration
    | variableSignatureDeclaration

Signature declarations give the type of a variable, they can be omitted. Variable declarations define functions/operators and named values. Note that all variable declarations have simple LHS’s, no argument matching is allowed (pattern matching is supported by the switch expression, see later).

variableSignatureDeclaration =
      ( simpleName | "(" operator ")" )
      ( variableDefinition | signatureDefinition )
    
variableDefinition = "=" expression

signatureDefinition = ":" typeExpression

Import declarations list the packages this package uses and maps directly to the equivalent for the platform. The “.*” extension is provided for the JVM, it is simply ignored for .NET.

importDeclaration = "import" qualifiedName [ "." "*" ]

Class declarations are the type declarations of Mondrian. Unlike OOP classes these do not have methods, Mondrian follows the traditional functional model of “data + functions”. However unlike traditional functional languages subtyping is supported:

classDeclaration = "class" simpleName [ "extends" qualifiedName] [ "{" variableDeclarations "}" ]

The traditional tagged disjoint union types of functional languages are modelled in Mondrian using subtyping. For example, the built-in list type is defined something like:

class List {};

class Nil extends List {}

class Cons extends List
{  head : Object;
   tail : List Object
};

(The “something like” is due to the lack of syntax for declaring parameterised types. The type system for Mondrian is still under development, “Object” is used to mean “any”. Yes this is a bit loose at present, but only type correct programs will execute, you just don’t get full compile time type checking. Sorry.)

The field declarations of classes, let-bound variables (see below), etc. can only have simple, non-operator, names:

variableDeclarations = ( simpleName variableDefinition  ";" )*
    
varDeclBlock = "{" variableDeclarations "}" | variableDeclarations

Standard Types

Mondrian’s standard types and their direct equivalents on the underlying OO platforms are:

Type .NET JVM
Integer boxed System.Int32 java.lang.Integer
Character boxed System.Char java.lang.Character
Boolean boxed System.Boolean java.lang.Boolean
Double boxed System.Double java.lang.Double
Void null null
String System.String java.lang.String
List, Cons/::, Nil/[] mondrian.lang.{List,Cons,Nil}

Note that all Mondrian types are “boxed” to support polymorphism.

Expressions

Expressions are of course central to Mondrian

expression = simpleExpression ( [ operator ] simpleExpression )*

simpleExpression =
      "-" simpleExpression
    | lambdaExpression
    | literal
    | parenthesizedExpression 
    | letExpression 
    | newExpression 
    | ifExpression
    | switchExpression
    | tryExpression
    | throwExpression
    | syncExpression
    | customDoExpression
    | doExpression
    | qualifiedName
    | nativeExpression
    | list

Functional application is denoted by juxtaposition and has the highest binding precedence. Operators have precedences and associativity as follows:

Operator Associativity
function application Left
*, /, % Left
+, - Left
<, >, <=, >= None
==, != None
&& Left
|| Left
user defined operators None
-> (lambda expressions) Right

Operators have their usual meanings, including “+” for String concatenation.

Defining Functions

Lambda expressions define functions. Functions are compiled to classes on the underlying OO platform, each class as a standard method which is called to evaluate the function. All functions notionally take one argument, of course multi-argument functions (e.g. a -> b -> expression) are compiled to multi-argument menthods. Function application is “curried” and functions may be partially applied. Parameters are passed non-strictly (i.e. parameters are not evaluated before the call but only when/if needed).

lambdaExpression = simpleName "->" expression

Conditional and Qualified Expressions

Standard “if” and “let” expressions are provided. The “let” supports recursive bindings and indeed the current compiler always produces them, so a “simplelet” is provided for when you don’t need them.

ifExpression = "if" "(" expression ")" expression "else" expression
      
letExpression = ( "let" | "simplelet" ) varDeclBlock "in" expression

Constructing Objects

Values of user-defined (class) type are constructed using new expressions. The keyword new is optional, write it if you’re an OO programmer, omit it if you’re a functional programmer! Field values are specified as a set of field name/value pairs.

newExpression = [ "new" ] qualifiedName [ "{" variableDeclarations "}" ]

Yes, There Is Pattern Matching...

The switch expression provides Mondrian’s pattern matching support. If the predicate expression is of discrete primitive type (Integer, Charac ter, Boolean) its value is used to select the branch, if it is of class type its subtype is used (i.e. instanceof/is style testing is used on the underlying platform). Patterns can only be simple, no multi-level patterns are provided (nest switch expression to achieve these). Local names for fields are specified using a series of local name/field name pairs.

switchExpression = "switch" "(" expression ")" alternatives

alternatives = "{" ( arm ";" )+ "}"

arm = ( "default" | "case" pattern ) ":" expression
    
pattern =
	   simpleName "::" simpleName
    | "[" "]"
    | literal
    | qualifiedName [ "{"(simpleName = simpleName ";" )+s "}" ]

Commands

Mondrian uses a monadic system for commands. Haskell-like do notation is provided, except there is no keyword is used (but see below):

doExpression = "{" ( [ simpleName "<-" ] expression ";" )* "}"

The expression’s must be of type IO a, the main function of a program must also be of this type. A library of standard I/O operations is provided.

Synchronized Command Blocks

Mondrian supports concurrent programming using threads, methods to create threads are provided in the library. To support synchronization between threads synchronized command blocks are provided. The simpleName must denote a variable of type mondrian.lang.Var, which are Mondrian’s updateable (monadic) variables (see library). Before the commands in the contained doExpression block are executed a lock is acquired on the simpleName, which is released on completion of the block. The library provides the requiste notify and wait functions. Threads, synchronized blocks, and the synchronisation primitives all map onto the equivalents on underlying platform. Mondrian can thus synchronize with other language threads, and vice-versa.

syncExpression = "synchronized" "(" simpleName ")" doExpression

Exceptions

Mondrian supports exceptions. The various contained doExpression’s must all be of the same type, IO a for some a, unless there is a finally block in which case the other blocks can be of different IO types. The type of the whole expression is that of the finally block, or if there is one the try/catch blocks. The throw expression is of type IO a. Mondrian exceptions are just platform exceptions, so any exceptions thrown by other code written in another language can be caught and vice-versa.

tryExpression = "try" doExpression
		( "catch" "(" simpleName ":" typeName ")" doExpression )*
		[ "finally"  doExpression ]

throwExpression = "throw" expression

Accessing “Foreign” Language Objects

Mondrian provides operations within the IO monad to create foreign language objects, invoke methods on such objects, and set and get fields (and properties on .NET) of those objects.

nativeExpression = 
  invokeExpression
  | createExpression
  | setExpression
  | getExpression

invokeExpression = "invoke" qualifiedName "(" [ nativeTypeName ( "," nativeTypeName )* ] ")"

createExpression = "create" qualifiedName "(" [ nativeTypeName ( "," nativeTypeName )* ] ")"

setExpression = "set" qualifiedName

getExpression = "get" qualifiedName

The result of an invoke or create operation is a value of type:

<tuple of native type> -> IO object/method result type

unless of course the method/constructor takes no arguments in which case the result is just of type:

IO object/method result type

Note that this is the only place tuples are used in Mondrian, you can construct them wherever you like but all you can (sensibly) do with them is pass them to the reuslt of an invoke or create. The set operation returns a value of type:

nativeTypeName -> IO Void

while the get operation is of type:

IO nativeTypeName

Values passed to native methods, constructors, or stored in native oject fields are first evaluated to “weak head normal form” (WHNF). This is because languages such as C# and Java are strict and do not understand unevaluated values.

The productiuon nativeTypeName is used as to avoid name clashes all Java keywords are also Mondrian keywords, even if they are not used by Mondrian. (C# has a convention to allow user name to clash with keywords so this is not an issue on .NET.)

nativeTypeName =
	"boolean"
	| "byte"
	| "char"
	| "double"
	| "float"
	| "int"
	| "long"
	| "short"
	| "void"
	| typeName

Odds’n’Ends

The rest should make sense, except the custom do and that is not documented - it’s here just so you know do is a keyword! Why do we have octal and hexidecimal constants? Well the parser generator we used provided them for free. Characters and strings support the usual escapes, floats look like floats, etc. The literal null exists as foreign language functions may return it or require it, think of it as “bottom”, maybe.

parenthesizedExpression = 
      "(" operator ")"
    | "(" expression ( "," expression )* ")"

list = "[" [ expression ( "," epxression )* ] "]"
	
customDoExpression = "do" doExpression

typeExpression = simpleType ( [ operator ] simpleType )*
      
simpleType =
      "(" typeExpression ")"
    | typeName

simpleName    = identifier             
typeName      = qualifiedName       
        
operator =
      qualifiers ( opChar )+
    | "`" qualifiedName "`"

opChar =
     "~" | "!" | "@" | "#" | "$" | "%" | "^"
   | "&" | "*" | "-" | "+" | "=" | "\" | "|"
   | "<" | ">" | "?" | "/" | ":"
 
qualifiers = ( identifier "." )*

qualifiedName = qualifiers identifier

literal = numLiteral | charLiteral | stringLiteral | boolLiteral | nullLiteral

numLiteral  =	intLiteral | octLiteral | hexLiteral | floatLiteral
nullLiteral = "null"
boolLiteral = "true" | "false"

Last modified: 15/10/2001