Control Flow (If-then-else)
The only control flow the language has is “if-then-else”.
The use of “if-then-else” is very intuitive.
A few examples will say it all.
Ø
if 10>2 then “yes” else “no”
yes
Ø
if #”hello” == 5 then 5 else “bad”
5
Ø
if “hello”@0=’e’ then 0
Ø
else if “hello”@1 == ‘e’ then 1
Ø
else “not found”
1
Unlike C/C++/Java, if-then-else is an expression. It can be used in any place where an expression is expected. For example:
Ø
(if 1>2 then 1 else 2)+3
5
Since if-then-else is an expression, the “if-then” statement in C/Java is not permitted. This is because you need a value for the “else” anyway.
Function
When I
said “if-then-else is the only control flow in Jaskell”, you may start
wondering “what kind of language is that?”
No, you
didn’t hear me wrong. Jaskell has no “for loop”, no “switch-case”, no
“throw-catch” in the langauge.
But, as
the ancient Entish motto says: “Don’t be hasty”.
These things,
however nice to have, can be achieved by using functions.
And now
we’ve arrived the most exciting part of Jaskell: function.
Function definition syntax
A simple
function definition is composed of a function name, a list of parameter names
and an expression as the body.
<fundef> ::= <fun name> (param_name)* ‘=’
<expr>
For
example:
Ø
add a b = a+b
Unlike
C/Java, parameters are neither enclosed by parenthesis, nor seperated by comma.
The
expression on the right side of the ‘=’ is the function body.
A
function can be parameterless. For example:
Ø
say_hello = println “hello”
If a
parameter is not used in the function body, it does not have to be named. the reserved ‘_’ character can be used as a placeholder for
such parameters.
For
example:
Ø
const c _ = c
The const
function will return the first parameter as return value and ignore the second.
In this case, the second parameter can remain unnamed and use ‘_’ as the
placeholder.
This is
pretty much the function definition syntax. When we introduce pattern match,
you will see more complex syntax for function definition.
Function call sytnax
Function
call syntax is very similar to function definition. Arguments are listed
directly after the function name. Unlike C/Java, neither parenthesis nor comma
is required.
<function_call> ::= <expr> (<expr>)*
For
example, to call the add function defined previously:
Ø
add 1 2
3
Thanks to
my friend, Eliminster. He made me realize that people with Java background may
still prefer to use “f(a,b,c)” syntax rather than “f a
b c”.
So,
Jaskell also supports the Javaish function call syntax.
To call add function, you can also say:
Ø
add(1,2)
Both syntax yield the same result. There’s no semantic difference between these
two syntaxes. In fact, you can even mix them. For example:
Ø
add 1 (2,3)
6
Ø
add(1,2) 3
6
Function call precedence
Function
call in jaskell has the highest precedence. So,
Ø
add 1 2 + 3
is
equivalent to
Ø
(add 1 2)+3
or
Ø
add(1,2)+3
Parenthesis
can be used to group expression for cases where the default precedence is not
what we want.
Parenthesis
can be used to group expressions.
Ø
add 1 (add 2 3)
6
Ø
add (1) (add 2 3)
6
Also, the
Javaish syntax can also be used to achieve the same goal.
Ø
add(1, add 2 3)
6
The ‘$’ operator
Except
for using parenthesis and Javaish syntax, there’s another way to change
precedence. The nice thing about this is, it is free of parenthesis and hence
can avoid code cluttering in some cases.
The
operator ‘$’ can be used to separate function and its argument.
f $ a has
the same semantics as f a. Except it
changes the precedence.
Ø
f a b c
is
equivalent to
Ø
f(a,b,c)
While
Ø
f $ a b c
is
equivalent to
Ø
f(a(b,c))
‘$’
operator is useful when the argument is a long expression. In that case, it can
save you from enclosing such long expression with parenthesis.
It is
also desirable when what we want is the following structure:
Ø
f1(a, f2(b, f3(c, f4(d, e))))
In this
case, the parenthesises are very ugly.
Using ‘$’,
the above expression can be written as:
Ø
f1 a $ f2 b $ f3 c $ f4 d e
This is
because ‘$’ is right-associative and has the lowest precedence.
Where clause
Jaskell
functions are defined with keyword “where”.
“where” is used to define functions and variables used in an
expression.
For
example:
Ø
add 1 2 where
Ø
add i j = i+j
3
More than
1 functions and variables can be defined in one “where” clause.
Ø
add 1 2 + sub 1 2 where
Ø
add a b = a+b;
Ø
sub a b = a-b;
2
“;” is
used to separate function definitions declared in the same “where” clause.
Unlike
haskell, indentation is not important in Jaskell. Of course it is always
desirable to align the code beautifully.
Scope
Definitions
in a where clause is only visible in that where clause and the expression
followed by that where clause.
This is
called static scope.
For
example, in the following expression,
Ø
E0 = E where
Ø
E = F1 where
Ø
F1 = Expr1;
Ø
F2 = Expr2;
Ø
F3 = Expr3;
F1, F2,
F3 are only visible in E, Expr1, Expr2 and Expr3.
It is
illegal to use F1, F2 or F3 in E0.
Nested where
An
expression followed by a “where” clause is nothing but another expression. As
an expression, it can be a sub-expression of a bigger expression, particularly,
it can be nested inside another “where” clause.
For
example:
Ø
f 2 = g1 2 + g2 2 where
Ø
g1 a = add a 1 where
Ø
add a b = a+b;
Ø
;. //line 4
Ø
g2 a = a+1;
6
Although
“;” is a seperator, not a terminator, which means it is optional for the last
definition in a where clause, it is mandatory in the case of nested where
clause to disambiguate.
If we omit
the “;” on line 4, jaskell will think the function g2 is only defined for the
nested where clause, and hence only visible within the scope of function g1.
And that is not what we wanted.
Declaration Order
The order
of the definitions in a where clause is not important. You can define functions
and variables in any order. It is legal to reference a function/variable
defined after the current definition.
For
example:
Ø
max 1 2 where
Ø
max a b = if bigger a b then a else
b;
Ø
bigger a b = a > b;
2
Lazy Evaluation
If definition
order is not important, which definition gets evaluated first?
What will
be the result of the following code?
Ø
say_hello where
Ø
say_hello = println “hello”;
Ø
say_bye = println “bye”;
hello
Don’t
worry about what “println” is. For now, let’s just assume it is a function that
prints message to the standard output.
You may
be wondering “where is ‘bye’”?.
The
reason why we don’t see “bye” is because jaskell is a lazy language.
Expressions are not evaluated until it is actually used.
In the
above code, only print “hello” needs
to be evaluated in order to evaluate say_hello. say_bye
is not requied and hence “bye” is not printed.
Because
of lazy evaluation, there’s actually no difference existing between a variable
and a function with 0 parameters.
In java,
we know say_hello() and say_hello are different. The
first one is a function call, it is evaluated every
time it is called. The second one is a variable whose value is already
evaluated. But jaskell does not make such distinction.
Recursion
Function
definition allows recursion. i.e. A function definition
can reference itself; two functions can reference each other, no matter who is
declared first.
The
following factorial function recursively calls itself and calculates n!.
Ø
fact 5 where
Ø
fact n = if n==0 then 1 else
n*fact(n-1);
120
Jaskell
even allow you to define such function:
Ø
infinite = infinite
In this
case, evaluating infinite will result
in infinite loop. But it is still valid if you define such function but never
evaluate it.
Side effect
Jaskell
is an impure functional language. One big difference between jaskell and pure
functional languages such as haskell is, it allows
side-effect.
“Side-effect”
includes all IO actions and any state change.
Side-effect
in Jaskell comes when calling Java methods, or when mutable data structures
such as Java array or function ref is used.
Now,
without going through the details of invoking Java, let’s assume we’ve defined
a println function that prints message to the standard output. Thus, function
println is a function with side-effect.
The sequencing operator “>>”
When
side-effect is permitted, evaluation order suddenly becomes important. Printing
“hello” then printing “world” is obviously different than print “world” then
printing “hello”.
As we
have shown, the definitions in a where clause are evaluated lazily. Who
executes first depends on who is evaluated first.
But who
is evaluated first?
For the
built-in operators such as “+”, “-“, “*”, “/”, left operand are evaluated
before the right operand gets evaluated.
But
still, in some cases, we may want to specify order manually.
Operator “>>”
is used to specify evaluation order.
Ø
expr1 >> expr2
The above
code is an expression, which, when evaluated, expr1 is evaluated first, and
then expr2 is evaluated and finally the result of expr2 is returned.
For
example, to print “hello” then print “world”, we can say:
Ø
println “hello” >> println “world”
hello
world
Call-by-need
In the “Lazy
Evaluation” section, we explained that functions in where clause
are evaluated lazily. The function body is evaluated everytime the
function is called (which means, the function call is evaluated), even if the
function has not parameter.
So, in
the following code, “hello” is printed twice.
Ø
say_hello >> say_hello where
Ø
say_hello = println “hello”;
hello
hello
What
about the following expression?
Ø
repeat say_hello where
Ø
repeat a = a >> a;
Ø
say_hello = println “hello”;
hello
The
result may be a bit surprising. Function repeat did not really do what we
wanted it to do. It only prints “hello” for once.
The
reason for this is:
Function
call in jaskell is call-by-need. That means, each function
argument is evaluated for at most once. Jaskell will save the result from the
first execution and return the same result for the subsequent calls.
For the
above code, say_hello is actually only evaluated once. The next evaluation of “a” only gets the return value of the last execution and does
not print anything.
Jaskell
uses call-by-need for performance concerns. And it is negligible if you stay
away from side-effects and stick to pure functional style.
But, hey,
we cannot say that you will never need to do such thing. In case you really
need to implement such repeat function, here’s what you can do to work around
it:
Ø
repeat run_say_hello where
Ø
repeat f = f [] >> f [];
Ø
run_say_hello _ = say_hello;
Ø
say_hello = println “hello”;
hello
hello
[] represents an empty list. We’ll cover the details in the “list”
section.
The
run_say_hello function now accepts one parameter. Although it does not use the
parameter, when passed as a function argument, it only evaluates to a function,
and hence will not stop you from calling it twice and print twice.
Let – in
However
flexible lazy evaluation is, sometimes, we may still want to strictly evaluate
an expression.
The let
statement can be used in this case.
Assume
function readline reads a line from a
file, function writeline writes a
line to a file, the following code reads one line from file1, one line from
file2, and write the two lines to file3.
Ø
let line1 = readline file1; //line 1
Ø
line2 = readline file2; //line 2
Ø
in
Ø
writeline file3 (line1+line2) //line 3
Similar
to where clause, let clause can also be used to define functions and variables.
The
syntax of defining functions and variables are exactly the same as in where
clause.
Variables
defined by let clause are strict. i.e. They are
eagerly evaluated when defined.
In the
above code, file1 is read when line 1 is interpreted; file2 is read when line 2
is interpreted.
Unlike where clause, declaration order of the definitions in let clause
is important.
Whoever defined first is executed first.
This also
means, variables and functions defined earlier cannot
reference variables and functions defined after them.
Another
difference between let clause and where clause lies in
how they handle duplicate names. In the same where clause, two functions
acannot share the same name. But let clause allows reusing the same name.
In imperative
languages such as Java, it is nornal to assign different values to the same
variable at different time.
Similar
effect can be achieved in jaskell by reusing the same name in let clause.
Ø
let
Ø
var = 1;
Ø
var = var+1;
Ø
in
Ø
var
2
This does
not introduce side-effect though. By re-binding a different value to the same
name, we are actualy creating a new binding with the same name, shadowing the
old name. But the value of the old binding is not changed. The following
example illustrates this point:
Ø
let
Ø
fun1 a = a+1;
Ø
fun2 x = fun1 x;
Ø
fun1 a = a*2;
Ø
in
Ø
fun2 10
11
This
shows the fun1 referenced by fun2 is still the old value after the
name “fun1” is re-bound to a different value. If the value of fun1 referenced in fun2 was changed, the result would have become 20.
Let
clause cannot define recursive function either.
I just
said the syntax of defining functions in let clause is the same as that of
where clause. Well, I lied. The reserved “_” character can be used in place of
a function/variable name when we care only the side-effect of an expression,
not its return value.
Ø
let a = action1;
Ø
_ = action2;
Ø
b = action3;
Ø
in
Ø
f a b
In the
above code, return value of action2 is ignored. But it is guaranteed that
action2 is executed between action1 and action3.
Similar
to where clause, let clause and the expression after keyword “in” is an
expression too. As an expression, it can be used anywhere an expression is
expected. This also means let clause can be nested.
Higher order function
Functions
in jaskell are fist-class citizen. They can be passed into another function as
arguments, they can be returned from any function.
The
following function takes as parameter another function, and calls the function.
It is also refered to as a call-back in languages such as C++ or Java.
Ø
apply f a = f a
The
follwing function takes two functions as parameters, returns a new function.
The new function, upon receiving argument, will call function f2, and then use
the return value of f2 as argument to function f1, it
finally returns the return value from function f1.
Ø
compose f1 f2 = new_f where
Ø
new_f a = f1(f2 a)
This
function represents the basic function composition.
Lamda Function
Jaskell supports
lamda function. A lamda function is an anonymouos function. It allows us not to
name a function when it is too trivial to create a named function.
For the
function composition example, we can rewrite it using lamda function as
follows:
Ø
compose f1 f2 = \a->f1(f2 a)
Character
‘\’ is used to indicate a lamda function. Parameter names are listed after ‘\’
and before ‘->’. The expression after ‘->’ is the function body.
For the
following compse2 function:
Ø
compse2 f1 f2 = new_f where
Ø
new_f a b = f1(f2 a b)
A lamda
function version will be:
Ø
compose2 f1 f2 = \a b->f1(f2 a
b)
Currying (Partial application)
Jaskell
supports partial function application. If a function is not given the number of
parameters declared, the function body is not evaluated. Instead, such partial
application results in a new function that expects the rest of the parameters.
For
example, function “add a b c = a+b+c”
expects three parameters.
If we
simply evaluate “add” without giving
it any argument, “add” is a function
expecting three parameters.
If we
evaluate “add 1”, it will be a function
expecting parameter b and c; argument 1 is remembered and bound to parameter a.
If we
evaluate “add 1 2”, it will be a
function expecting parameter c; argument 2 is remembered and bound to parameter
b.
Conceptually,
calling add 1 2 3 is no different than
calling ((add 1) 2) 3.
We can
say add 1 2 3 is a function call with
3 arguments. At the same time, we can also say it is three subsequent function
calls with 1 argument each.They are exactly the same thing.
For the
compose function defined earlier, it can be rewritten as:
Ø
compose f1 f2 a = f1 (f2 a)
Because
of currying, it is no different than
Ø
compose f1 f2 = \a->f1(f2 a)
Operator as function
Jaskell
operators can be treated as functions and used in a prefix
syntax.
Simply
enclosing an operator in a pair of parenthesis will make it a function.
The
following add function:
Ø
add a b = a+b
can be
simplified as an operator function: (+)
Ø
(+) 1 2
3
The ‘$’
operator introduced earlier, is a function application operator. If used as a
function, is exactly the same as the apply
function.
Ø
apply (add 1) 2 where
Ø
apply f a = f a
3
Ø
($) (add 1) 2
3
Function as operator
An
operator can be used as a function in a prefix syntax.
Similarly, a function can be used as an operator in an infix
syntax.
The
syntax is simple. Enclosing an expression within a pair of backquote (`), will
make it an infix binary operator.
Ø
1 `add` 2 where
Ø
add = (+)
3
a `f` b is
equivalent to f a b.