Mystique

by David Huynh, July 21, 2003

mys-tique: (www.m-w.com)

Pronunciation: mi-'stEk
Function: noun
Etymology: French, from mystique, adjective, mystic, from Latin mysticus
2 : the special esoteric skill essential in a calling or activity

Mystique is a programming language designed to wire up Haystack UI. Its core features include:

  1. Native syntax for RDF, XML, and XHTML data
  2. Native syntax for information closures
  3. Long-running execution
  4. Function currying
  5. Typing and integration with "slobjects"
  6. Exception handling
Mystique's features may also be convenient for writing agents and services that react to changes in the RDF store.

Syntactic Changes from Adenine

Mystique inherits some features from Adenine, such as native syntax for RDF resources, RDF literals, and RDF models. However, these features have been changed as detailed below.

1. Infix notation is adopted in preference over Adenine's prefix notation for the reason of readability and familiarity.

2. Prefixed URIs in Mystique have equal expressiveness as full URIs because their suffixes can be delimited by angle brackets. The following URI cannot be prefixed in Adenine

<http://haystack.lcs.mit.edu/homepage.html>

as the period preceding "html" is interpreted as a dereferencement operator. In Mystique, that URI can be prefixed as follows:

@prefix haystack: <http://haystack.lcs.mit.edu/>
haystack:<homepage.html>

There should be no whitespace between the colon and the left angle bracket. Note that the angle brackets are only mandatory for suffixes that contain unusual characters.

3. A full URI starts with < but it must be followed immediately by a non-whitespace character. Otherwise, < is tokenized as a symbol.

4. Statement separators (semicolons in Adenine) are eliminated in favour of subject separators (periods in Mystique). Here are the same code in Adenine and then in Mystique:

# in Adenine
:foo
:a :b ;
:c :d ;
:e :f
:bar
:x :y ;
:u :v ;

# in Mystique
:foo
:a :b
:c :d
:e :f .
:bar
:x :y
:u :v .

This change is motivated by the observations that consecutive statements added have the same subject more often than not, and that one period is easier to manage while editing than several semicolons. Programmers have become very inventive in battling the semicolons in Adenine, as evident in the following formatting style:

:foo :a :b
; :c :d
; :e :f

Semicolons will still be used in Mystique but for a different purpose.

5. Annotations can be made on any resource by following it with an attribute container delimited by @{}. Anonymous URIs can be generated using the $ operator. Example:

:me
:have $ @{ rdf:type :Car }
:have ${ rdf:type :House } . # short form

:Joe :have (car = $) @{ rdf:type :Car } .
:Mary :want car

This also means that annotations on methods don't have to be flushed against the left margin:

method :addToCollection @{
rdf:type op:Operation
rdf:type dnd:DNDOperation
dc:title "Add To Collection"
} (
:collection @{
rdf:type dnd:DropParameter
dc:title "Collection"
} = c,
:item @{
rdf:type dnd:DragParameter
dc:title "Item"
} = i
)
...code...

(although the concept of methods is different in Mystique).

6. Mystique files can be annotated by @{} at top level and can have 2 additional processing instructions: @version and @encoding. Example:

@version "1.0"
@encoding "UTF-8"

@prefix dc: <http://purl.org/dc/elements/1.1/>
@{
dc:author <dfhuynh@ai.mit.edu>
dc:date "July 16, 2003"
}

...rest of file...

7. Statement annotations can be made using the ; operator. This operator in essence makes the most recent statement the subject of interest. Example:

:Joe
:likes Mary ; :assertedBy :Mike dc:date "June 20th, 2003" .
rdf:type hs:Person .

Note that this example must use a period to talk about the subject :Joe again.

Containers

There are several kinds of syntactic containers in Mystique. The syntax inside the braces for each type of container can be unique to that type of container.

1. RDF statement containers can be delimited with statements:{} or simply with {}.

2. RDF attribute containers can be delimited with attributes:{}. When used with the @ operator, @ attributes:{} can be shortened to @{}. When used with both the $ and @ operators, $ @ attributes:{} can be shortened to ${}.

3. Query condition containers can be delimited with conditions:{} or simply with %{}.

4. XML containers can be delimited with xml:{}. The content in between the braces can be any valid XML fragment enclosed in one outermost XML element. Example:

xml:{ <span>Hi, my name is <b>David</b>.</span> }

5. XHTML containers can be delimited with xhtml:{}.

6. Information closure containers can be delimited with closure:{}. Example:

closure:{
extends another closure,
asserts statements:{ some statements },
denies statements:{ some statements } where conditions:{ some conditions }
}

(Information closures encapsulate truths in reified form so that they don't interfere with the absolute truths in the store.)

Functions

Mystique has (free) functions instead of methods (although Adenine methods are not really methods either). Functions are named by URIs. A function takes zero or more parameters and returns zero or more results. Parameters and results can be positional or named by URIs, just like in Adenine. Example:

function :foo (
Integer x,
Float y optional,
Boolean z as :flag,
Resource w as :default
) returns (
Resource u,
Literal v
)

if not(isNull(y))
if x * 2 > y
u = :daffodil
else
u = w
else
u = :grass

v = "Flower"

Functions are automatically curried. A function is evaluated when all of its required parameters (those not marked with optional) have been provided. (This feature makes it unnecessary to have anonymous methods as in Adenine.)

A Mystique function can be executed in the long-running mode. That means that when some variables the function depends on change, the function is re-executed from the point in the function code where the changes first affect. In the previous example, if the parameter w is changed, then if it is the case that y is not null and twice x is less than or equal to y, then the second assignment statement is re-executed.

The parameter variables of a function are immutable by the function. The function can change only its local variables and its result variables (u and v in the previous example). Changes to a function's result variables may cause some piece of code in its caller to be re-executed.

In addition to local, parameter, and result variables, a function can also accesses environment variables, which are dynamically scoped. You have to declare which environment variables a function accesses using the accesses keyword:

@importJava "edu.mit.lcs.haystack.security.Identity"
function hs:getUser returns (Resource userURI) accesses (Identity __identity__)
userURI = __identity__.uri

The long-running mode in which Mystique functions are executed can be convenient for implementing agents and services that react to changes in the system. The following service classifies new email messages:

function :classifyMailAgent @{
rdf:type hs:Agent
dc:title "Classify Mail Agent"
} accesses (IRDFDataAccessPoint __rdfDAP__)
Resource message = __rdfDAP__.extract(
?x, rdf:type, mail:Message,
conditions:{ rdfstore:notContains(?x, :processed, "true") }
)

if message != null
:classifyOneMessage(message)

__rdfDAP__.add(message, :processed, "true")

Let us assume that the implementation of __source__ is smart enough such that whenever the RDF store changes such that the result of the method call extract is changed, the method call is re-executed. We force the result of extract() to change by asserting processed messages.

Control Flow

A function is executed until there is no more code left to execute, or if there is a stop statement. The following function implements the short-circuit and:

function mystique:and (Boolean x, Boolean y) returns (Boolean z)
if x
if y
z = true
stop
z = false

If y is changed and x has been true, then the second if is re-executed. The stop statement acts like a return statement in other languages. However, since the execution might not actually return to the function's caller, the return keyword is not used.

Sometimes it is desirable to ignore changes to some particular variables or to actively observe changes to some variables even if a segment of code does not refer to them. The following function

function :toggle(
Boolean value,
UIEventSource eventSource
) returns (
Boolean value2
)
value2 = value
observe (eventSource) ignore (value2)
value2 = not(value2)

toggles its output value upon any change to eventSource, even though the second assignment statement does not refer to eventSource. Note that if the parameter value is changed, then the result value2 is set to equal value; the second assignment statement, while dependent on value, is not re-executed because it has been marked to ignore changes to value. We can hook this function up to a checkbox, giving it the default value of the checkbox and an event source that detects mouse clicks.

One might also need to mark a code section to run only once when a function is invoked (its stackframe is constructed) or once when a function is terminated (its stackframe is destroyed). This can be done using the initially-normally-finally construct:

function foo(...) returns(...) accesses(...)
/* ...only variable declarations here... */

initially
/* ...code that runs once when the function is invoked... */
normally
/* ...normal code that may react to changes... */
finally
/* ...code that runs once when the function is terminated... */

Looping

for-in and while are supported. Example:

for RDFNode node in results
...

However, loops are generally discouraged in Mystique in favour of functions like map and apply. This is because a generic loop has to be unrolled so that changes trigger only a subset of its iterations.

Exceptions

try-catch-finally and throw are supported just like in Java. Functions should also be declared with throws should they throw any exception. Example:

function parse(...) returns(...) accesses(...) throws(SyntaxException, RDFException)
...

try
...
catch NullPointerException e
...
throw new(SyntaxException("Missing semicolon"))
finally
...

...

Note that the finally clause in this construct behaves differently than the finally clause in the initially-normally-finally construct. In this case, the clause can be executed many times and does react to changes.

Typing, Objects, and Slobjects

You must have noticed by now that Mystique is statically typed. This is a difference from Adenine and is intended to aid optimization as well as improve code readability. We can also rely on the type signature of a function to construct an appropriate UI to present it to the user.

Mystique is object-based rather than object-oriented because while one can call methods on (Java) objects, one cannot declare new classes in Mystique.

Java objects can be constructed by calling the new function passing in a "constructor function". In the following example,

new(SyntaxException("Missing semicolons"))

the function SyntaxException() has been constructed automatically by the Mystique compiler based on the reflection information on the Java class of the same name. This function, known as the constructor function, when called with a string, returns a curried function that is then called by the new function.

The new function can take an optional Resource parameter named mystique:Allocation if the constructed object is a "slobject" and its URI has been allocated before. Example:

new(Person("David" as person:name), :me as mystique:Allocation)

Persistence

Adenine makes it super-easy to add data to the store and this feature tends to confuse novice Adenine programmers (as traditional programming requires explicit effort to persist data). This convenience of Adenine leads to coding sloppiness as everything is then put into the store without further thought as to which actually needs to be persisted.

It is my hope that with the various abstractions that Mystique provides, programmers no longer need to use the rdf store as a scratchpad for persisting tidbits of miscellaneous temporary system data.

Optimization

Mystique is not designed for extensibility as Adenine was. You cannot add new instruction handlers or new constructs. This is a feature, chosen to facilitate optimization. After all, there has been no evidence that new instruction handlers will ever be added to Adenine by Adenine programmers.

The dynamism of Mystique will possibly cause Mystique code to be slow. However, this dynamism is needed for the UI and must be implemented in some way anyway. It is better to have a language abstraction so that optimizations can be applied systematically by the framework rather than individually by UI programmers (who may happen to be inexperienced with optimizations).