Expressions

Equality

Mantra's == operator always invokes equals() and tests content equality whereas in Java it tests object identity and primitive value, two different operations.

x==3 // tests value
s=="abc" // tests value (unlike java)
names==["a", "b"] // tests list value equality
user1==user2 // tests object equality

To test identity, Mantra has a new operator: is, which is translated to == in Java.

// walk linked list
p = head;
while ( p isnt tail ) { p = p.next; }
...
if ( user1 is user2 ) { // same object in memory?
 ...
}

Use is not == to test for null as null is the empty object and you want to test identity:

if ( p is null ) return null;

Method call

Method calls look just like they do in Java:

println("hi"); // assumes this.println("hi");
a = list.get(3);
o.name(a1,a2,a3);

If you would like to pass a closure (see next section) to a method as the last argument, you can use an alternative syntax. Do not put the closure inside the parentheses, but instead put the closure block after the function call:

t = time() { println("hi"); };
println(t); // prints "hi" and how long in milliseconds closure took

Closures

A closure is just a code block or perhaps an anonymous method. Methods and closures are considered first class objects in that they may be assigned as variable values and so on. Closures have zero
or more arguments just like a method and can have return values:

func c = {int x | println(x);};
c.call(3); // prints "3\n"

Here is an example return value:

func c = {int x | return x+1;};
int y = c.call(3); // y is 4

Closures without arguments look just like code blocks (i.e., there is no '|' symbol).

Closures may specify a conditional that restricts their applicability. In other words they will only execute when that condition is true:

c = {int x where x>0 | return 100/x;};
println(c.call(5)); // prints 20
println(c.call(0)); // prints null

This is very similar to the preconditions espoused by the "programming by contract" languages such as Eiffel. This is most useful when used with the map operator in the next section.

Map operator

Mantra has a built-in "map" operator that applies a closure or method to a stream of objects (type InputStream). If the left operand is not a stream, the map operator asks if it knows how to become a stream via toInputStream(). If that returns null, Mantra wraps that object in a single element generic object string to make the expression execute in a natural fashion.

The most common use of the map operator will be to apply a code block to a data structure. For example the following code prints out the elements of a list using a closure and the map operator:

a = [5,10];
a:{x | println(x);};

Or, equivalently:

a = [5,10];
c = {x | println(x);};
a:c;

The map operator is actually using a.toInputStream() here as the left operand. You can also use a method name in the place of a closure (works as long as the method accepts a single argument):

a=[5,10]
a:println;

A string is converted to a stream of characters if you use it on the left side of the map operation:

"abc":{c| println(c);}; //prints "abc"

File objects also get turned into streams of characters:

f = File("data.txt");
f:{c| print(c);}; // print all chars in the file
f:print; // same thing

Multiple map operators can be chained together with left associative precedence:

a = [1,2,3];
b = a:{x | return x+1;}:{x | return x*2;};
println(b); // prints "[4, 6, 8]"

If the closure returns a value, those values are collected and placed into a list, which in turn becomes a stream again for the next map operation.

The following closure application to a list counts the number of elements of values that are greater than zero:

n = 0;
values:{x where x>0 | n++;};

There is an implicitly-defined loop iteration variable, i, that is available in any closure:

"abc":{char c | println(i);};

emits:

0
1
2

The variable is properly nested too:

list a = ["abc", "def"];
a:{string s |
        print(i.toString()+":");
        s:{char c | print("<"+i+":"+c+">");};
        println("");
};

emits:

0:<0:T><1:e><2:r><3:e><4:n><5:c><6:e>
1:<0:S><1:r><2:i><3:r><4:a><5:m>

List comprehensions

To create a new list from an old list using a closure is easy with the map operator:

a = [1,2,3];
b = a:{x | return x+1;}; // b = [2,3,4]

Or,

managers = names:{n; db.isManager(n) | return n;};

Breaking out of a map operation

The break statement breaks out of the immediately enclosing loop:

while ( ... ) {
      if ( ... ) break; // jump out of loop
}

This is just like Java. Now, if you say "break expression", however, it implies you are breaking out of the immediately enclosing loop of a map operation:

string s = "apple";
list result = s:{char c where c=='p' | break i;};
println(result[0]);

emits "1", the index of the first 'p'.

The condition c=='p' means that the break instruction will execute only when the map iterates to the 'p'. The body then immediately breaks the loop and returns i, the implicitly-defined iteration variable.

Closure lexical scoping

Closures have access to the variables of the surrounding method scope whereas a method cannot see the local variables of another method. This allows enclosures to modify local variables. The following code counts how many elements of list values there are:

int n = 0;
values:{int x | n++;};

This true even when the closure is returned from the method and used outside of that method:

func f() {
  int x = 3;
  return {println(x);};
}
...
func c = f();
c.call(); // prints 3

Weird, but consistent. Either you have to keep checking for an invalid stack frame or you just let it happen. Faster to allow.

Operators

There is no new operator. Instance creation is done with classname(...) like a call to their constructor; this mirrors python. For example, File("foo") creates a File object.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.