As many other programming languages, Jaskell supports number literals, character literals and string literals. 1, 3.5, "abc", 'x' are all valid literals.
A list is a sequential data structure.
Except for string literal enclosed by double quote, Jaskell supports
here-docs syntax as well.
i.e. almost any character can appear in the string.
Two different syntax are supported for here-docs:
$$<<hello $name>>$$ where
name="Tom";
end
evaluates to "hello Tom".
Any non-alpha character can be picked as the leading and terminating
character for here-docs string literal.
The leading character is the character following "<<<" or "$$<".
The terminating character is the character followed by ">>>" or ">$$".
The following expressions all evaluate to "hello":
Special rules apply for '<', '>', '[', ']', '{', '}', '(', ')'.
These characters have a natural matching character, so if you pick any one of them
as the leading character, the matching character needs to be the terminating character.
<<<|hello|>>>
<<<!hello!>>>
<<<-hello->>>
<<<^hello^>>>
$$<|hello|>$$
$$<!hello!>$$
$$<-hello->$$
$$<^hello^>$$
The following expressions all evaluate to "hello":
<<<<hello>>>>
<<<(hello)>>>
<<<{hello}>>>
$$<<hello>>$$
$$<(hello)>$$
$$<{hello}>$$
String literal denoted by double quote and "$$<<...>>$$" style supports string interpolation.
In such string literal, a word prefixed by a '$' is interpreted as a variable name. The string representation of this variable will be evaluated and embedded in the string at runtime.
Also, an expression enclosed by '${' and '}' is evaluated and the string representation of the value will be embedded in the string at runtime.
For example:
evaluates to "hello Tom".
"hello $name" where
name="Tom"
end
evaluates to "I said: hello Tom".
$$<<I said: ${greeting "Tom"}>>$$ where
greeting name = "hello $name";
end
A tuple is an associative array.
',' or ';' can be used to seperate tuple members.
Tuple member can be de-referenced with a '.'. For example:
evaluates to "tom".
{name="tom"; age=1}.name
if-then-else is the only native conditional statement in Jaskell.
Unlike many imperative languages such as Java, the "else" clause is mandatory.
The following expression evaluates to 5:
if 1==1 then 1 else 5
There's no native switch-case support in the Jaskell programming language.
However, the 'jaskell.prelude.switch' function can be used for this purpose.
For example:
evaluates to "one".
switch (3-2)
.case(0, "zero")
.case(1, "one")
.default("unknown")
.end
Similar to Java,
Jaskell supports the following binary operators with natural semantics:
'==', '!=', '>', '<', '>=', '<=', '+', '-', '*', '/', '%', '&&', '||', 'and', 'or'.
Slightly different from Java though, '==' and '!=' calls "Object.equals()", and comparison operators such as '>', '<' calls "Comparable.compareTo()".
Hence, the following expressions all evaluate to true:
'and' and '&&', 'or' and '||' are equivalent.
"abc"=="abc"
"abc"!="ABC"
"10"<"2"
"abc"=="abc" and "abc"!="ABC"
"abc"!="ABC" or "10"=="2"
Unary operators such as '!', '~', 'not' are also supported. 'not' and '!' are equivalent. They are both the logical negation operator. '~' reads "negative". "~1" is same as "-1".
Jaskell supports ':', '++', '@', '#' operators for list operation.
':' is used to prepend an element to a list.
evaluates to [1,2,3]
1:[2,3]
'++' is used to concatenate two lists together:
evaluates to [1,2,3,4]
[1,2]++[3,4]
'@' is used to get an element from a list/tuple by index/key:
evaluates to 2.
[1,2,3,4]@1
evaluates to 10.
{name="tom";age=10}@"age"
Besides (@), jaskell also supports the familiar "[]" notion as a syntax sugar
to indicate a list/array subscript. For example,
also evaluates to 2, which is exactly the same as the (@) operator.
[1,2,3,4][1]
Another use of the "[]" syntax sugar is to indicate an array type, so one can say:
int[]
'#' is used to get the size of a list/tuple:
evaluates to 2.
#[1,2]
For array and list, the "length" method can also be called to read the length,
which is more familiar to most Java programmers:
[1,2].length
'let' is a keyword that allows definition of variable and function.
is an expression that evaluates to 3.
let
a = 1;
b = 2;
a+b;
end
'let' clause is an expression that's terminated by an 'end' keyword.
Enclosed by 'let' and 'end' are a list of statements.
Each statement can be either an expression or a definition.
When the "let-end" expression is evaluated, the statements are evaluated sequentially.
The value of the last statement is the value of the "let-end" expression.
Statements are seperated by a ';'.
Recursion is not allowed in "let-end".
will report an error because the recursive use of name "fact" is not recognized.
let
fact n = if n==0 then 1 else n*fact(n-1);
end
In fact, only names defined prior to the current statement are visible.
The same name can be re-defined though.
For example:
At line 3, the name "a" is redefined so that the value of c becomes "2+b".
And since line 3 is a "re-definition", not "assignment", the value of b
is not changed.
Therefore, the above expression evaluates to 4.
let
a = 1;
b = a+1;
a = 2; //line 3
c = a+b;
end
'where' is another keyword to define variables and functions.
Enclosed by 'where' and 'end' are a list of definitions.
Recursion is allowed in the definitions. In fact, all names defined in "where-end" clause are visible to each other.
Unlike "let-end", re-definition of the same name is not allowed.
The order of the definitions does not matter.
For example:
evaluates to 5.
a+b where
a = b+1;
b = 2;
end
The following code defines a simple function that adds two values together:
Function parameters can be listed directly after the function name and seperated by whitespace.
add a b = a+b;
An alternative syntax is more like Java:
where parameters are enclosed by a pair of parenthesis and seperated by ','.
add(a,b) = a+b;
When a parameter is not used in the function body, its name may be omitted.
In this case, the special symbol '_' can be used to indicate an anonymous placeholder.
For example:
const v _ = v;
It is possible to omit function name. Such function is called "lamda".
Symbol '\' is used to denote a lamda function.
is equivalent to
add where add a b = a+b end
\a b -> a+b
When defining function, different function body can be provided based on the pattern of certain parameters.
Symbol '|' is used to seperate different patterns.
For example, the following function reverse a list:
reverse [] = [] //in case of empty list
| x:xl = reverse xl ++ [x]; // in case of non-empty list.
The following function tests if a parameter is a tuple with a member named "ind":
hasMember {ind} = true
| _ = false;
It is possible to name a pattern, so that this name can be used
to reference the object of this pattern.
Special symbol '@' can be used to name a pattern.
The following function returns the tuple itself if it contains member "ind",
an empty tuple is returned otherwise:
test t@{ind} = t
| _ = {};
Similar to function definition, there're two alternative syntax for calling a function.
For the following "add" function:
add a b = a+b;
is equivalent to
add 1 2
add(1,2)
Jaskell function can be curried. i.e.
Functions can be partially applied. For example:
let
add a b = a+b;
inc = add 1;
inc 2;
end
Function "inc" is obtained by giving function "add" only one parameter.
The result is a closure that holds this parameter value and waits for the arrival of the missing parameter.
"inc 2" provides the missing parameter and finally apply the function "add".
Operator '$' reads as "apply".
f $ 1 is equivalent to f 1 where the right operand is passed to the left operand as function parameter.
The '$' operator looks useless at the first glance because literally adding a '$' in between
the function and the parameter makes no difference at all.
The value of '$' operator is to change the precedence.
f1 f2 x is equivalent to (f1 f2) x.
But if we need to pass x to function f2 first, the code has to be changed to
f1 (f2 x), which requires a parenthesis to force the precedence.
Such parenthesis may become ugly in more complex expressions.
In such case,
'$' can help.
'$' is an operator with the lowest precedence, f1 $ f2 x is equivalent to f1(f2 x).
Operator '<<' reads as "compose".
f1 << f2 creates a function whose first parameter is passed to f2,
and the return value of f2 is passed to f1. Thus f1 $ f2 x is equivalent
to (f1<<f2) x.
Operator '>>' is used to enforce evaluation order.
When the following definition is evaluated, expr1 is first evaluated, then expr2 is evaluated.
This is useful when expr1 has side-effects.
expr = expr1 >> expr2;
Jaskell functions are call-by-need.
A parameter is not evaluated until needed. For example:
will evaluate to 5 because the "invalid" parameter is never evaluated.
const 5 invalid where
invalid = invalid;
const v _ = v;
end
functions and variables can be defined in three different contexts: where clause, let-end expression and a tuple.
Although the syntax looks very similar, different rules apply.
In 'where' clause:
In "let-end" expression:
In a tuple declaration:
Any operator can be used as a function by enclosing it in a pair of parenthesis.
For example: (+) 1 2 is equivalent to 1+2.
Although '-' can also be used as the unary negative operator, (-) is
always treated as the binary "minus" operator.
In order to get the unary "negative" operator, use (~) instead.
Jaskell allows using function as an infix binary operator.
The backward apostrophe '`' can be used to denote an infix function.
For example:
evaluates to 3.
1 `add 2 where
add a b = a+b
end
When the function used as infix operator is a more complex expression,
parenthesis is needed to enclose the expression, for example:
evaluates to 6.
1 `(add3 2) 3 where
add3 a b c = a+b+c;
end
A tuple can be "sliced" to create a new tuple that is a subset of the source tuple.
evaluates to {name="tom";age=10}.
{name="tom";age=10;sex="male"}.{name,age}
Note, slicing a tuple does not evaluate any tuple member.
A tuple can be "extended" to create a new tuple with new members included.
evaluates to {name="tom";age=10;sex="male"}.
{name="tom";age=10}.{sex="male"}
Note, extending a tuple does not evaluate any tuple member.
As we mentioned earlier, tuple member names are not visible as function names
to the member bodies.
The following expression reports error:
because "firstname" and "lastname" are not visible to the definition of "fullname".
{firstname="Tom"; lastname="Swan"; fullname="$firstname $lastname"}
In order to reference these two members of the current tuple, the
special variable 'this' has to be used:
The member "fullname", when evaluated, will evaluates to "Tom Swan".
{firstname="Tom"; lastname="Swan"; fullname="${this.firstname} ${this.lastname}"}
Since 'this' always binds to the current tuple, it is essentially a late-binding mechanism.
For example:
will evaluate to "Jack Swan" because 'this' now points to a tuple whose firstname is "Jack".
{firstname="Tom"; lastname="Swan"; fullname="${this.firstname} ${this.lastname}"}
.{firstname="Jack"}.fullname
The "tuple.{firstname=...}" syntax can be used to statically extend a tuple.
It is also possible to dynamically merge the members of two tuples together.
'jaskell.tuple.extends' and 'jaskell.tuple.includes' are two functions for this purpose.
For example, the following two expressions both evaluate to
{firstname="Tom"; lastname="Swan";} :
{firstname="Tom"} `extends {lastname="Swan"}
{firstname="Tom"} `includes {lastname="Swan"}
The difference is how 'this' is bound:
The 'extends' function binds 'this' to the new tuple for all members;
While 'includes' binds 'this' for each member to the original tuple that owns the member.
So the following expression using 'extends' is legal:
while the following expressiong using 'includes' is not:
({firstname="Tom"} `extends {lastname="Swan", fullname="${this.firstname} ${this.lastname}}})
.fullname
({firstname="Tom"} `includes {lastname="Swan", fullname="${this.firstname} ${this.lastname}}})
.fullname
Any Java class can be imported using the 'jaskell.java.import' function.
For example:
jaskell.java.import "java.lang.StringBuffer"
Each Java class supports a special member named "new" to create an object of this class
by calling one of its constructors.
For example:
StringBuffer.new[]
For convenience, a global "new" function is also provided to mimic the Java "new"
operator. Therefore the above code can be written as:
new StringBuffer[]
In order to get the class of an array type, the 'jaskell.java.array' function can be used.
For example:
will create an int[] object with size 5.
intarray.new 5 where
intarray = jaskell.java.array int;
end
The "[]" syntax sugar can also be used so that the above example can be re-written as:
int[].new 5
For any Java object, its public field can be referenced in Jaskell.
For example, for the following class in Java:
A Person object's field can be referenced in Jaskell as:
class Person{
public String name;
public int age;
...
public void setName(String n){...}
public Person(String n, int a){...}
}
person.name
To invoke methods or constructors, the parameters need to be put into a list.
For example:
or
person.setName["Jack"]
Person.new["Tom", 10]
Java bean properties can be read either by calling the getter method,
or, more conveniently, by referencing the property name directly as if
it were a field.
For example, for the following class in Java:
In order to get the value of the "name" property of a person object,
the code can simply say:
class Person{
private String nm;
private int age;
...
public String getName(String n){return nm;}
public Person(String n, int a){...}
}
person.name