Using The Jaskell Programming Language
Jaskell is a lazy functional scripting language for Java. It features higher-order function, function currying, string interpolation, lazy evaluation, monad, dynamic typing, simple syntax and semantics etc.
- Literals
- list
- here-docs string literal
- string interpolation
- tuple
- if-then-else
- switch-case
- Operators
- let
- where
- function
- lamda function
- pattern match
- Calling function
- function currying
- ($) operator
- (->) operator
- (=>) operator
- (:=) operator
- (<<) operator
- (>>) operator
- call-by-need
- where vs. let vs. {}
- operator as function
- function as operator
- slicing tuple
- extending tuple
- 'this' variable
- 'extends' and 'includes'
- import Java class
- new
- array
- object
- java bean
- Jaskell API Documentation
Literals
As many other programming languages, Jaskell supports number literals, character literals and string literals. 1, 3.5, "abc", 'x' are all valid literals.
list
A list is a sequential data structure.
- [] denotes an empty list.
- [1,2] denotes a list with two elements.
- [1,["abc",'x']] is a list whose first element is a number and the second element is another list.
here-docs string literal
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:
- The $$<<...>>$$ style allows string interpolation so that
evaluates to "hello Tom".
- The <<<<...>>>> style suppresses string interpolation.
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.
The following expressions all evaluate to "hello":
string interpolation
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".
evaluates to "I said: hello Tom".
tuple
A tuple is an associative array.
- {} is an empty tuple.
- {name="tom"; age=1} is a tuple with two members.
- {str="abc", list=[1,2,'3'], tuple={}} is a tuple with 3 members.
',' or ';' can be used to seperate tuple members.
Tuple member can be de-referenced with a '.'. For example:
evaluates to "tom".
if-then-else
if-then-else is the only native conditional statement in Jaskell.
Like many imperative languages such as Java, the "else" clause is optional. When 'else' clause is omitted, 'null' is used as the value of this clause.
The following expression evaluates to 5:
switch-case
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".
Operators
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.
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]
'++' is used to concatenate two lists together:
evaluates to [1,2,3,4]
'@' is used to get an element from a list/tuple by index/key:
evaluates to 2.
evaluates to 10.
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.
Another use of the "[]" syntax sugar is to indicate an array type, so one can say:
'#' is used to get the size of a list/tuple:
evaluates to 2.
For array and list, the "length" method can also be called to read the length, which is more familiar to most Java programmers:
'=>' operator is a variant of "if-then'. "cond => x" is equivalent to "if cond then x". '=>' is typically overridden to provide "deduction" kind of logic.
':=' is an operator with no predefined meaning. It can be overloaded to provide custom semantics though.
let
'let' is a keyword that allows definition of variable and function.
is an expression that evaluates to 3.
'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.
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.
where
'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.
function
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.
An alternative syntax is more like Java:
where parameters are enclosed by a pair of parenthesis and seperated by ','.
These two syntax are totally equivalent though.
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:
lamda function
It is possible to omit function name. Such function is called "lamda". Symbol '
' is used to denote a lamda function.
is equivalent to
pattern match
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:
The following function tests if a parameter is a tuple with a member named "ind":
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:
Calling function
Similar to function definition, there're two alternative syntax for calling a function.
For the following "add" function:
is equivalent to
A new syntax added since version 0.9 is the "->" operator. "f a" and "f(a)" are equivalent to
This new syntax helps when function calls are chained, because "a->h->g->f" is obviously easier to read than "f(g(h(a)))".
function currying
Jaskell function can be curried. i.e. Functions can be partially applied. For example:
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
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
Operator '->' reads as "arrow".
1->f is equivalent to f 1 where the left operand is passed to the right operand as function parameter.
'->' operator is used to provide an alternative syntax for calling function, wherever it looks fit.
(=>) operator
Operator '=>' reads as "then".
a=>b is equivalent to if a then b where the left operand is the condition and the right operand is the consequence expression.
'=>' operator is typically overriden to provide custom "deduce" kind of logic.
(:=) operator
Operator ':=' is an operator with no predefined meaning. It needs to be overriden either globally or in a tuple to be used. It is typically to implement 'assignment' kind of logic.
(<<) operator
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
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.
call-by-need
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.
where vs. let vs. {}
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:
- recursion is allowed;
- the order of definition does not matter;
- re-definition of the same name is disallowed;
- names are visible to each other;
- each name is evaluated in a by-need fashion. i.e. A name is not evaluated until needed, and it is evaluated for at most once.
In "let-end" expression:
- recursion is disallowed;
- names are only visible to the definitions after them;
- statements are evaluated in declaration order;
- re-definition of the same name is allowed.
In a tuple declaration:
- names are not visible to each other;
- a member body is evaluated every time the member is evaluated;
- re-definition of the same name is disallowed.
operator as function
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.
function as operator
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.
When the function used as infix operator is a more complex expression, parenthesis is needed to enclose the expression, for example:
evaluates to 6.
slicing tuple
A tuple can be "sliced" to create a new tuple that is a subset of the source tuple.
evaluates to {name="tom";age=10}.
Note, slicing a tuple does not evaluate any tuple member.
extending tuple
A tuple can be "extended" to create a new tuple with new members included.
evaluates to {name="tom";age=10;sex="male"}.
Note, extending a tuple does not evaluate any tuple member.
'this' variable
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".
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".
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".
'extends' and 'includes'
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";} :
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:
import Java class
Any Java class can be imported using the 'jaskell.java.import' function.
For example:
new
Each Java class supports a special member named "new" to create an object of this class by calling one of its constructors.
For example:
For convenience, a global "new" function is also provided to mimic the Java "new" operator. Therefore the above code can be written as:
array
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.
The "[]" syntax sugar can also be used so that the above example can be re-written as:
Note, Jaskell doesn support the "new int[5]" syntax currently.
object
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:
To invoke methods or constructors, the parameters need to be put into a list.
For example:
or
java bean
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:
