# Getting started
SCL Console is the easiest way to access SCL in Simantics. Therefore we will
be using it all the time in this tutorial.
----
SCL Console is opened from the menu *Show View/Other/SCL Console*.
\includegraphics[scale=0.4]{figures/ShowView.png}
\hspace{1cm}
\includegraphics[scale=0.4]{figures/ShowView2.png}
The view that is opened looks like this:
\includegraphics[scale=0.5]{figures/SCLConsole.png}
SCL commands are written to the box at the bottom of the view and the command responses
are shown in the upper part of the view. Try writing
~~~
> 1 + 1
~~~
and press enter.
----
If the current command in the SCL Console input box contains an error, it is shown with a red underline.
Moving the mouse pointer to that position shows an error description:
\includegraphics[scale=0.5]{figures/ConsoleError.png}
When the command contains an errors, the console is not willing to execute it. Pressing *enter*
adds a new line to the command.
While editing the command, arrow keys can be used to move the cursor around.
Pressing *ctrl-enter* adds a new line even if the command is currently valid.
Pressing *ctrl* and arrow keys up or down, you can browse the previously entered commands.
Also copy *ctrl-C*, paste and cut work in the editing area.
----
### Exercise
Run a multiline command:
~~~
> for [1..10] (\x ->
for [1..x] (\y ->
print (show x + " + " + show y + " = " + show (x+y))
)
)
~~~
Then find it from the history and modify it to show multiplications instead of additions.
----
### Remark
We will show lots of examples of SCL code in this tutorial. If some explanation leaves
you in uncertain state, copy some of the examples to the console and
play with them.
## Basic expressions
We start from the basic mathematical expressions you can write in SCL.
This should cover the most needs you have if you want to use SCL expressions
in component types.
----
Writing mathematical formulas in SCL is mostly unsurprising.
~~~
> 3*7+1
22
> 2 - sqrt 2
0.5857864376269049
> cos pi
-1.0
~~~
One difference from many programming languages is that parentheses are not required around the
parameters and multiple parameters are separated by whitespace:
~~~
> atan2 1 1
0.7853981633974483
~~~
Parentheses are needed for each parameter separately, if the parameter is
more complicated expression than constant or variable:
~~~
> atan2 (sin 0.4) (cos 0.4)
0.4
~~~
----
### Exercise
What is wrong with the following code. How to fix it?
~~~
> sin -1
~~~
----
The results of the computations can be bound to variables and used in subsequent expressions:
~~~
> m = 4.0
> c = 2.998e8
> energy = m * c^2
> energy
3.5952016E17
~~~
If the variable is bound multiple times, the latest definition is used:
~~~
> a = 4
> a = 8
> a
8
~~~
----
### Remark
Replacing a variable binding with a new binding is a feature of SCL Console.
In SCL modules, variables can be bound only once.
----
A variable name must begin with a *lower-case* letter or an underscore.
Following that can be any number of letters (lower-case and upper-case), numbers and underscores.
The following names are reserved and cannot be used as identifiers:
`_`, `forall`, `if`, `then`, `else`,
`where`, `by`, `do`, `mdo`, `class`, `effect`,
`match`, `with`, `instance`, `deriving`,
`data`, `type`, `infix`, `infixl`, `infixr`.
`import`, `include`, `importJava`, `as`.
----
This is the list of arithmetical functions available in SCL. The middle column
contains the types of the functions. You can ignore them now.
zero | Additive a => a | additive neutral element |
(+) | Additive a => a -> a -> a | addition |
sum | Additive a => [a] -> a | sum of list elements |
neg | Ring a => a -> a | negation |
(-) | Ring a => a -> a -> a | difference |
one | Ring a => a | multiplicative neutral element |
(*) | Ring a => a -> a -> a | multiplication |
fromInteger | Ring a => Integer -> a | converts an integer to another number format |
abs | OrderedRing a => a -> a | absolute value |
(/) | Real a => a -> a -> a | division |
(`^`) | Real a => a -> a -> a | exponentiation |
pi | Real a => a | $\pi$ |
sqrt | Real a => a -> a | square root |
exp | Real a => a -> a | exponent function |
log | Real a => a -> a | natural logarithm |
sin,cos,tan | Real a => a -> a | trigonometric functions |
asin,acos,atan | Real a => a -> a | inverse trigonometric functions |
atan2 | Real a => a -> a -> a | arctangent with two parameters |
sinh,cosh,tanh | Real a => a -> a | hyperbolic functions |
asinh,acosh,atanh | Real a => a -> a | inverse hyperbolic functions |
fromDouble | Real a => Double -> a | converts a double to another number format |
floor | Real a => a -> a | the largest previous integer |
ceil | Real a => a -> a | the smallest following integer |
div | Integer a => a -> a -> a | integer division |
mod | Integer a => a -> a -> a | integer remainer
|
Their meaning is mostly self-explanatory.
Real and integer divisions are two different functions:
~~~
> 5 / 2
2.5
> 5 `div` 2
2
~~~
The hat `^` is exponentiation:
~~~
> 3^4
81.0
~~~
Negation function `neg` can be used with the unary minus syntax. So the following are equivalent:
~~~
> neg 3
-3
> -3
-3
~~~
----
### Remark
At this point you may encounter the following problem:
~~~
> a = 2
> b = 3.4
> a + b
~~~
The console does not execute the last expression because it contains an error
``Expected $\langle$Integer$\rangle$ got $\langle$Double$\rangle$''.
This is because the variable `a` is bound to an integer value and `b` is bound to a double value
and SCL does automatic number conversion only for numerical literals (2, 3.4 etc.). There are several ways to fix
the problem. First is to define `a` as a double:
~~~
> a = 2.0
~~~
or
~~~
> a = 2 :: Double
~~~
Another way is to convert the integer value to double in the final expression:
~~~
> fromInteger a + b
5.4
~~~
----
Numbers and many other objects can be compared with the following functions:
(==) | Eq a => a -> a -> Boolean | equality |
(!=) | Eq a => a -> a -> Boolean | inequality |
(<),(>) | Ord a => a -> a -> Boolean | strict comparison |
(<=),(>=) | Ord a => a -> a -> Boolean | non-strict comparison |
min | Ord a => a -> a -> a | returns the the smaller of two parameters |
max | Ord a => a -> a -> a | returns the larger of two parameters |
compare | Ord a => a -> a -> Integer | returns -1 (0, 1), if the first parameter is smaller (equal, bigger) than the second one
|
For example:
~~~
> 1 < 2
True
> 6 < 4
False
> max 1.5 3.8
3.8
~~~
----
Boolean expressions can be used to write conditional expressions:
~~~
> a = 3
> b = 4
> if a < b then a else b
3
> a = 5
> if a < b then a else b
4
~~~
In order to test multiple conditions, if-expressions can be combined:
~~~
> if a < b
then -1
else if a > b
then 1
else 0
1
~~~
----
Boolean values can be combined with conjunction `&&`, disjunction `||` and negation `not`.
~~~
> True && False
False
> True || False
True
> not True
False
~~~
----
Strings are written by enclosing them into double quotes:
~~~
> "Hello world!"
"Hello world!"
~~~
Some of the operators seen so far can also be applied to strings:
~~~
> "Hello" + " world!"
"Hello world!"
> "Foo" < "Bar"
False
~~~
There are also many functions specific to strings. We will however
describe just some of them:
length | String -> Integer | the length of a string |
substring | String -> Integer -> Integer -> String | returns a substring of the string
when the begin and the end positions of the
string are given |
show | Show a => a -> String | converts a value to a string |
read | Read a => String -> a | converts a string to a value |
indexOf | String -> String -> Integer | the index of the first position in the string that matches the given string |
lastIndexOf | String -> String -> Integer | the index of the last position in the string that matches the given string |
startsWith | String -> String -> Boolean | checks if the string starts with the given prefix |
trim | String -> String | removes the white space from the beginning and end of the string
|
From these functions, we need only `show` in this tutorial.
~~~
> x = 5.7
> "x = " + show x
x = 5.7
~~~
## Functions
In SCL, functions are first-class values that can be manipulated with the
same ease as numbers and other values. Almost all control structures also
require that you define your own functions. Therefore we describe them
before going to other data structures.
----
The syntax of the function definitions in SCL resemble function definitions in mathematics:
~~~
> dist x y = abs (x - y)
> dist 2 5
3
> dist 6 4
2
~~~
----
### Exercise
Define a generalized average function:
$${\rm avgG}\ p\ x\ y = \left({x^p + y^p \over 2}\right)^{1 \over p}$$
Check that it works correctly:
~~~
> avgG 1 3 5
4.0
> avgG 2 2 14
10.0
> avgG (-1) 3 6
4.0
~~~
----
Functions can be recursive. For example Euclid's algorithm for
computing greatest common divisor for two integers can be implemented
as:
~~~
> gcd a b = if a > b
then gcd b a
else if a == 0
then b
else gcd (b `mod` a) a
> gcd 12 9
3
~~~
----
### Exercise
Implement a function `fib` for computing Fibonacci numbers. First two Fibonacci numbers
are one and after that every Fibonacci number is the sum of two previous Fibonacci numbers:
1, 1, 2, 3, 5, 8, 13, \ldots.
----
Many functions are implemented by cases. In these situations function definition can be
written as multiple separate equations:
~~~
> gcd a b | a > b = gcd b a
gcd 0 b = b
gcd a b = gcd (b `mod` a) a
~~~
In this way it is easier to verify that every individual equation is corrent.
There are two ways to restrict the applicability of an equation.
The first way is to use patterns as parameters instead of just variables.
In the example above, the second equation has a constant pattern 0 as a first parameter.
We see later more complicated patterns.
The second way to restrict the applicability is to add guards to the equation.
A guard is the part in the first equation above after the vertical bar and before the equality.
It is a boolean condition that must be true for equation to be applicable.
The actual function value is computed by the first equation that can be applied with the given parameters.
----
### Exercise
Rewrite the Fibonacci's function using either constant patterns or guards.
----
One of the cornerstones of all functional programming languages, including SCL, is the ability
to give functions as parameters to other functions:
~~~
> epsilon = 1e-9
> der f x = (f (x + epsilon) - f (x - epsilon)) / (2*epsilon)
> der sin 0
1.0
> der log 5
0.2000000165480742
~~~
This kind of functions are called *higher order functions*.
Here is another example:
~~~
> iterate f n x | n > 0 = iterate f (n-1) (f x)
| otherwise = x
> collatz n | n `mod` 2 == 0 = n `div` 2
| otherwise = 3*n + 1
> iterate collatz 0 13
13
> iterate collatz 1 13
40
> iterate collatz 2 13
20
> iterate collatz 5 13
16
> iterate collatz 8 13
2
> iterate collatz 9 13
1
~~~
----
Often the function we want to define can be get by filling some parameters of an already existing function.
For example a function `limitZero`, that returns zero if its parameter is negative and otherwise
returns the parameter unmodified, can be defined as:
~~~
> limitZero = max 0.0
> limitZero 2.1
2.1
> limitZero (-5.4)
0.0
~~~
This is called *partial application*.
----
Binary operators can be referred and partially applied by enclosing them
in parenthesis:
~~~
> f = (+)
> f 1 2
3
> incByOne = (+) 1
> incByOne 5
6
~~~
Inversely, an ordinary function can be used as a binary operator by enclosing it
to backquotes (in fact, we have seen it already in the definiton of `gcd`):
~~~
> 1 `f` 2
3
> 14 `div` 3
4
~~~
----
Functions can be also defined without giving them a name:
~~~
> \x -> x * 2
> (\x -> x * 2) 3
6
~~~
This is useful when using higher order functions:
~~~
> der (\x -> x^2 - 5*x) 2
-1.000000082740371
~~~
----
For writing complex functions, it is necessary to define auxiliary
variables. There are two ways of doing this in SCL. The first way is
`where` syntax:
~~~
> dist x1 y1 x2 y2 = sqrt (dx*dx + dy*dy)
where
dx = x1-x2
dy = y1-y2
~~~
The second way is `do` syntax
~~~
> dist x1 y1 x2 y2 = do
dx = x1-x2
dy = y1-y2
sqrt (dx*dx + dy*dy)
~~~
The difference between these constructs is often only aesthetic.
One feature of SCL that is imporant here is that SCL uses
the layout (indentation) of the code for parsing. The statements
in the same `where` or `do` block must be consistently
indented and indentation must be larger than the enclosing context.
----
SCL standard library defines the following functions for
manipulating functions:
id | a -> a | identity function |
(\$) | (a -> b) -> a -> b | application operator |
(.) | (b -> c) -> (a -> b) -> (a -> c) | function composition |
const | a -> b -> a | constant function |
curry | ((a, b) -> c) -> a -> b -> c | give function parameters separately instead of a pair |
uncurry | (a -> b -> c) -> ((a, b) -> c) | inverse of curry |
curry3 | ((a, b, c) -> d) -> a -> b -> c -> d | give function parameters separately instead of a tripl |
uncurry3 | (a -> b -> c -> d) -> ((a, b, c) -> d) | inverse of curry3 |
flip | (a -> b -> c) -> b -> a -> c | flips the order of the function parameters
|
Function `$` just applies given function and a parameter:
~~~
f $ x = f x
~~~
The precedence of the operator is very weak and it associates
to right. It can be used
to remove some parentheses in deeply nested expression. So
we can write
~~~
> text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
> length
$ filter (\s -> length s > 4)
$ splitString text "[ ,.]"
5
~~~
instead of
~~~
> length (filter (\s -> length s > 4) (splitString text "[ ,.]"))
5
~~~
## Data structures
----
One of the simplest data structure is a pair:
~~~
> ("Citizen Kane", 1941)
("Citizen Kane", 1941)
~~~
As seen above, the different elements of the pair can have different types.
The components of the pair can be accessed with the functions `fst` and `snd`:
~~~
> fst ("Citizen Kane", 1941)
"Citizen Kane"
> snd ("Citizen Kane", 1941)
1941
~~~
Other tuple lenghts are also supported:
~~~
> ()
()
> ("1", 1, 1.0)
("1", 1, 1.0)
~~~
----
Pairs and other tuples are useful, when we want to return more than one value from a function:
~~~
> toPolarCoordinates (x,y) = (sqrt (x*x + y*y), atan2 y x)
> toRectangularCoordinates (r,angle) = (r*cos angle, r*sin angle)
> polar = toPolarCoordinates (3,-6)
> polar
(6.708203932499369, -1.1071487177940904)
> toRectangularCoordinates polar
(3.000000000000001, -6.0)
~~~
As seen in the above example, when tuples are given as parameters to the functions, they can
be pattern matched. In `toPolarCoordinates (x,y)` the components of the pair given
as a parameter to the function are bound to `x` and `y`.
A much less readable version of the above function would be:
~~~
> toPolarCoordinates p = (sqrt (fst p*fst p + snd p*snd p),
atan2 (snd p) (fst p))
~~~
Patterns can be nested. A nonsensical example:
~~~
> shuffle ((a,b), (c,d)) = ((c,b), (a,d))
> shuffle ((1,2), (3,4))
((3, 2), (1, 4))
~~~
----
### Remark
The following command produces an error ``Expected $\langle$Integer$\rangle$ got $\langle$String$\rangle$
~~~
> shuffle (("1","2"), ("3","4"))
~~~
althought the function definition doesn't seem to care about the contents of the values (even if they are all of different types).
This is a restriction of the current version of SCL Console: all functions you define are *monomorphic* meaning
that all types involved must be fully known. When this is not a case, the compiler chooses the types somewhat arbitrarily.
In SCL modules, there is no such restrictions and the compiler chooses the most generic type as possible for a function.
Therefore, if the `shuffle` were defined in a SCL module, the above command would work.
----
Maybe the most important datatype is a list that may
hold any number of values of the same type. Lists
are constructed by enclosing the values in brackets:
~~~
> [1, 2, 3, 4]
[1, 2, 3, 4]
~~~
Elements of the lists are accessed with `!` operator:
~~~
> l = [1, 2, 3]
> l!0
1
> l!2
3
~~~
The length of the list can be found out with `length`:
~~~
> length l
3
~~~
Two lists are joined together with `+` operator:
~~~
> [1, 2] + [3, 4]
[1, 2, 3, 4]
~~~
Finally, a list containing some range of integers can be constructed as
~~~
> [4..9]
[4, 5, 6, 7, 8, 9]
~~~
----
### Exercise
A quadratic equation $a x^2 + b x + c = 0$ has real solutions only if the discriminant
$\Delta = b^2 - 4ac$ is non-negative. If it is zero, the equation has one solution and
if it is positive, the equation has two solutions:
$${-b \pm \sqrt{\Delta} \over 2a}.$$
Write a function $`solveQuadratic`$ that solves a quadratic function whose
coefficients are given as parameters and returns a list containing all solutions
of the equation:
~~~
> solveQuadratic 1 1 (-2)
[1.0, -2.0]
> solveQuadratic 1 1 1
[]
~~~
----
Many useful tools for manipulating lists are higher order functions.
The function `map` applies a given function to all elements of a list
and produces a new list of the same length from the results:
$$`map`\ f\ [x_1,x_2,\ldots,x_n] = [f\ x_1,f\ x_2,\ldots,f\ x_n]$$
Here is couple of examples:
~~~
> map (\s -> "_" + s + "_") ["a", "b", "c"]
["_a_", "_b_", "_c_"]
> map ((*) 3) [1..4]
[3, 6, 9, 12]
~~~
Unwanted elements can be removed from a list by `filter` function:
~~~
> filter (\s -> length s > 4) ["small", "big", "large", "tiny"]
["small", "large"]
> isPrime p = filter (\d -> p `mod` d == 0) [2..p-1] == []
> filter isPrime [2..20]
[2, 3, 5, 7, 11, 13, 17, 19]
~~~
The function `foldl` computes an aggergate value over a list of values
by applying a binary function to the elements starting from the given initial element:
$${\tt foldl}\ (\oplus)\ x_0\ [x_1,x_2,\ldots,x_n] = (\cdots((x_0 \oplus x_1) \oplus x_2) \oplus \cdots \oplus x_n)$$
In practice:
~~~
> foldl (+) 0 [2,3,4]
9
> foldl (*) 1 [2,3,4]
24
> foldl max 0 [2,3,4]
4
~~~
The function `sum` is equivalent to `foldl (+) zero`, but is in
some cases implemented much more effeciently (for example for strings).
The function `concatMap` is similar to `map`, but it assumes
that the given function produces lists and concatenates the lists together:
~~~
> concatMap
(\(n,v) -> map (const v) [1..n])
[(3,"a"), (2,"l"), (1,"i")]
["a", "a", "a", "l", "l", "i"]
~~~
----
### Exercise
Define a generalized average function for lists:
$${\rm avgG}\ p\ [x_1,\ldots,x_{n}] = \left({x_1^p + \cdots + x_n^p \over n}\right)^{1 \over p}$$
----
### Exercise
One useful way for producing lists is to map a range expression. For example
~~~
> diffs (l :: [Integer]) = map (\i -> l!(i+1) - l!i) [0..length l-2]
> diffs [1,2,4,8,3]
[1, 2, 4, -5]
~~~
Use the pattern to reimplement the function `reverse` from the standard library
that reverses the order of elements in a list.
----
SCL supports *list comprehension* which allows to write
many list producing expressions with a syntax that closely
resembles the set comprehension from mathematics:
~~~
> l = [1..3]
> [2*x | x <- l]
[2, 4, 6]
> [(x,y) | x <- l, y <- l]
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]
> [(x,y) | x <- l, y <- l, x < y]
[(1, 2), (1, 3), (2, 3)]
> [(x,y) | x <- l, y=4-x]
[(1, 3), (2, 2), (3, 1)]
~~~
As seen in the above examples, the left side of the vertical bar
can be any expression producing the elements of the list. The
right side of the bar is a list of producing statements:
`x <- l` considers all `x` in the list `l`,
`x = e` sets the value of `x` by the expression `e`
and any boolean expression like `x < y` filters the produced elements.
----
### Remark
SCL does not support yet pattern matching of lists althought it is supported in Haskell.
----
This is a list of some list functions provided by SCL standard library:
map | Functor a => (a -> b) -> f a -> f b | |
concatMap | (a -> [b]) -> [a] -> [b] | |
filter | (a -> Boolean) -> [a] -> [a] | |
filterJust | [Maybe a] -> [a] | |
foldl | (a -> b -> a) -> a -> [b] -> a | apply the given binary operation from left to right |
foldl1 | (a -> a -> a) -> [a] -> a | |
unfoldl | (b -> Maybe (a, b)) -> b -> [a] | |
unfoldr | (b -> Maybe (a, b)) -> b -> [a] | |
zip | [a] -> [b] -> [(a,b)] | combine elements in the two lists |
zipWith | (a -> b -> c) -> [a] -> [b] -> [c] | |
unzip | [(a,b)] -> ([a],[b]) | |
sort | Ord a => [a] -> [a] | sorts the elements in the list |
sortBy | Ord b => (a -> b) -> [a] -> [a] | sorts the elements by a given criteria |
sortWith | (a -> a -> Integer) -> [a] -> [a] | sorts the elements by a given comparison function |
(\\) | Eq a => [a] -> [a] -> [a] | removes from the first list the elements that occur in the second list |
range | Integer -> Integer -> [Integer] | |
reverse | [a] -> [a] | reverses the order of the elements in the list |
take | Integer -> [a] -> [a] | returns $n$ first elements of the list |
drop | Integer -> [a] -> [a] | removes $n$ first elements from the list and returns the tail |
lookup | Eq a => a -> [(a, b)] -> Maybe b | finds a pair from the list such that the first component
matches the given value and returns the second component |
and | [Boolean] -> Boolean | |
or | [Boolean] -> Boolean | |
any | (a -> Boolean) -> [a] -> Boolean | |
all | (a -> Boolean) -> [a] -> Boolean
|
----
One data structure used quite often is an optional value.
It is useful, if the function produces the result only for
some parameter values. Optional value is either
`Just value` or `Nothing`. Here is an example:
~~~
> safeSqrt x | x >= 0 = Just (sqrt x)
| otherwise = Nothing
> safeSqrt 2
Just 1.4142135623730951
> safeSqrt (-2)
Nothing
~~~
The type of the expressions `Just value` and `Nothing` is `Maybe t`,
where $`t`$ is the type of `value`.
Optional values can be handled with pattern matching
(the example is from the standard library):
~~~
> fromMaybe _ (Just value) = value
fromMaybe default Nothing = default
~~~
----
Sometimes it is useful to use pattern matching without
defining a new function for it. Therefore there is
`match` construct:
~~~
> match (1,2,3) with
(x,y,z) -> x+y+z
6
~~~
As with functions, there can be multiple cases and the
first matching case is chosen:
~~~
> match safeSqrt 3 with
Just v -> v
Nothing -> fail "Something bad happened."
~~~
## Types
----
Every expression in SCL has a type. Type determines the set of possible values
of the expression. They are used for detecting errors in SCL code, for choosing the
right implementation of a method (for example addition `(+)`) and for generating
efficient byte code.
When the type is monomorphic (does not contain type variables), it can be printed as
~~~
> typeOf "foo"
String
> typeOf [1,2,3]
[Integer]
~~~
----
Normally SCL compiler will infer the types of the expressions automatically and
you don't have to specify them manually. There are however situations where
explicit control of types is useful:
* You want to document the interface of your functions. It is good practice
to write a type annotation for all top-level functions declared in a SCL module.
Such declarations are not available in SCL Console:
~~~
fst :: (a,b) -> a
fst (x,y) = x
~~~
* SCL compiler cannot infer a type that satisfies all the required constraints:
~~~
> strId x = show (read x)
There is no instance for .
There is no instance for .
> strId x = show (read x :: Integer)
> strId "1"
"1"
> strId "1.0"
java.lang.NumberFormatException: For input string: "1.0"
...
> strId x = show (read x :: Double)
> strId "1"
"1.0"
> strId "1.0"
"1.0"
~~~
The type annotation is needed here, because `read` parses a string
to a certain type of values, `show` converts it back to string,
and the behavior of the composition depends on what the intermediate type is.
There is however not enough information for the compiler to infer the type
(it is shown as `?a` in the error message).
* Code specialized to a certain type may be more efficient than a generic code
that applies to all data types.
A type of an expression or identifier is annotated by adding `::` and a type
expression after it. Type annotations may also be used in patterns:
~~~
sign (x :: Double) = compare x 0
~~~
----
The following types (or more exactly, type constructors) are built in the SCL compiler:
* `Boolean`
* `Byte`, `Short`, `Integer`, `Long`
* `Float`, `Double`
* `String`
* `[]`
* `()`, `(,)`, `(,,)`, ...
* `(->)`
* `Maybe`
* `Array`
* `BooleanArray`, `ByteArray`, `ShortArray`,
`IntegerArray`, `LongArray`, `FloatArray`, `DoubleArray`
Other type constructors are defined in SCL modules, either importing them from Java
or using `data` declaration.
Except for the couple of special cases in the previous list, the names of
all type constructors are capitalized.
Some type constructors are parametric (compare to generics in Java or templates in C++).
For example, the list type constructor `[]` has one parameter: the type of the list
elements. Thus `[Integer]` is the type of the integer lists and `[String]`
is the type of string lists.
`[[Integer]]` is the type of the lists of integer
lists. Parameters are usually written after the parametric type constructor:
for example `Maybe Integer` or `Array String`, but some of the builtin type
constructors can be written in a special way in order to make the type
expressions more readable:
~~~
[a] = [] a
(a,b) = (,) a b
(a,b,c) = (,,) a b c
...
a -> b = (->) a b
~~~
Particularly important type constructor is `(->)` for building function types.
For example, the type of the function computing the length of a string is `String -> Integer`:
the function takes a string as a parameter and returns an integer.
Types of the functions taking multiple parameters are written by composing function types.
For example, the type of a function taking nth element of a string list is
`[String] -> Integer -> String`. The function takes a string list and an integer as
a parameter and returns a string. Function type operator `->` is right associative
thus the previous type is equivalent to `[String] -> (Integer -> String)`.
Thus the type expression can be read as well as a type of functions taking a string
list and returning another function from integers and strings.
`(a,b)` is the type of pairs where the first component of the pair has type `a`
and the second component has type `b`. Tuple types `(a,b,c)`, `(a,b,c,d)` etc.
are defined similarly. `Maybe a` is the type of optional values.
----
Many functions can be defined so that they do not need to know the exact types they are operating with.
Such unknown types can be filled in type expressions by type variables: for example the function
we discussed earlier that took nth element of a string list does not need the information that
list elements are strings in its implementation and so it can be given a more generic type
`[a] -> Integer -> a`, i.e a function taking a list of `a`:s and an integer as a parameter
and returning a single `a`.
Type variables may also refer to parametric types, but all useful examples involve type constraints we describe below.
----
### Exercise*
Function types with type variables tell quite much about the function assuming it is total,
i.e does not hang or throw a runtime exception with any parameters. Write the *unique*
functions with the following type signatures:
~~~
a -> a
(a,b) -> (b,a)
~~~
Write all possible functions with the following type signatures:
~~~
a -> a -> a
~~~
Because SCL Console does not support definition of functions with generic types,
this exercise can be done only by writing your own SCL module.
----
SCL does not support function overloading at least in the way Java and C++ support it.
This means that every function has a unique type signature. Now consider the addition function
`(+)`. We could defined its signature for example as `Integer -> Integer -> Integer`,
but then we would need some other name for the sum of doubles `Double -> Double -> Double` or
strings `String -> String -> String`. It would seem like the right signature would be
`a -> a -> a`, but that is not satisfactory either, because then the function had to
somehow support all possible types.
The solution to the problem is to constraint the set of allowed types for the type variable.
Such a constrained type is written in the case of addition function as
`Additive a => a -> a -> a`. The constraint `Additive a` is composed of a type class
`Additive` followed by parameters of the constraint.
The type can be understood in two different ways. A logical reading is
> For any additive type `a`, the function takes two `a`:s as parameters and returns `a`.
and an operational reading that gives a good intuition what is happening in runtime
> The function takes a parameter `Additive a` that describes a set of methods that can be used to operate the values of `a` and two `a`:s and returns `a`.
Constrained type variable can also be parametric. For example, let's say we want to define a function getNth that takes nth element of a list but also works with arrays. The signature would then be
~~~
getNth :: Sequence s => s a -> Integer -> a
~~~
Type classes form a hierarchy: each class may have any number of superclasses. Only the most specific type class is needed in the constraints. For example, addition and multiplication have the following signatures:
~~~
(+) :: Additive a => a -> a -> a
(*) :: Ring a => a -> a -> a
~~~
and Additive is a superclass of Ring a. A function using both operators need to specify only Ring as a constraint:
~~~
doubleAndAddOne :: Ring a => a -> a
doubleAndAddOne x = 2*x + 1
~~~
This is a hierarchy of ``numerical'' type classes defined in the standard library of SCL:
\includegraphics[scale=0.6]{figures/TypeClasses.png}
The white boxes are type classes. An open arrow between type classes is the inheritance between type classes.
The yellow boxes are types defined in the standard library. They are connected to the type classes
they belong to.
There are also type classes for converting values to strings `Show` and vice versa `Read`.
Types in the type class `Hashable` provide a method to compute a hash code.
Types in the type class `Serializable` can be serialized to a byte array and deserialized back to a value.
There are also some type classes that make programming with different containers more generic:
`Functor`, `Monad`, etc.
----
SCL functions are referentially transparent which means that they are like
mathematical functions always returning the same value for the same parameters
and not causing any observable side-effects. This seems extremely restrictive
because most of the programs are written in order to generate some kind of
side-effects and even completely mathematical algorithms involve functions
that are not referentially transparent such as generation of random numbers.
This policy does not however restrict expressive power of the language because
functions can return and manipulate *descriptions of computations*.
These computations are then executed by some external means.
Two different ways of handling computations in SCL are monads and effect types.
Effect types are not applicable to as large range of different
computation concepts as monads are but they are easier to work with.
An effectful computation has type ` t`.
The type after angle brackets is the type of the value obtained by the computation.
Side-effects that might happen during the computation are listed inside of angle brackets.
Effect types must always occur as a return type of a function. Here are some examples (some fictional)
of functions with side-effects:
~~~
randomBetween :: Double -> Double -> Double
resource :: String -> Resource
claim :: Resource -> Resource -> Resource -> ()
randomLiteral :: Double -> Double -> Resource
~~~
Effect types are much like type constraints: you can mostly ignore them when using functions.
All effects you use are automatically collected and added to the type signature of the defined
function (or checked against type annotation if provided).
Like type classes, effects form a hierarchy. For example WriteGraph inherits ReadGraph
and a function both reading and writing to graph is annotated only with `` effect.
Like types, effects can also be abstracted with effect variables. This is important when defining generic
functions that manipulate possibly effectful functions:
~~~
executeTwice :: (() -> ()) -> ()
executeTwice f = do f () ; f ()
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
~~~
----
### Exercise
Revisit the earlier lessons listing library functions and their types.
Can you now understand all the type signatures?
## Side-effects
----
So far our examples have used pure functions without side effects.
There are indeed quite few functions in the standard library producing
side effects. One of them is the printing function:
~~~
> print "Hello world!"
Hello world!
~~~
Side-effectful function applications can be sequenced
in a `do` block:
~~~
> do print "Hello,"
print " World!"
Hello,
World!
~~~
----
It is very typical that we want to cause some effect for
every element of some list. For that purpose there is a function `for`:
~~~
> for [1..3] print
1
2
3
~~~
Here is a little more complicated example:
~~~
> items = ["patridge in a pear tree", "turtle doves",
"french hens", "colly birds", "gold rings",
"geese-a-laying", "swans-a-swimming", "maids-a-milking",
"ladies dancing", "lords-a-leaping", "pipers piping",
"drummers drumming"]
> numbers = ["and a", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", "eleven", "twelve"]
> printVerse n = do
print "On the twelfth day of Christmas, my true love gave to me"
for [1..n] (\i -> do
c = n-i
print $ (if n==1 then "a" else numbers!c) + " " + items!c
)
print ""
> for [1..12] printVerse
...
~~~
----
### Exercise
Procude the lyrics of the song ``99 Bottles of Beer''
.
----
Sometimes it is useful to introduce a state that can be modified
during an algorithm. Such a state is created with function `ref`,
it is accessed with `getReg` and modified with `(:=)` operator:
~~~
> nRef = ref 1
> for [1..8] (\_ -> do
n = getRef nRef
print n
nRef := 2*n
)
1
2
4
8
16
32
64
128
~~~
----
### Exercise
Reproduce the `gcd` function by using modifible state.
----
This is a partial list of functions in the standard library producing side-effects:
\setlength\LTleft{-2cm}
for | FunctorE f => f a -> (a -> ()) -> () | executes the given function with every element in the structure |
print | Show a => a -> () | prints the given value |
ref | a -> (Ref a) | creates a state |
getRef | Ref a -> a | accesses a state |
(:=) | Ref a -> a -> () | modifies the state |
newArrayList | () -> ArrayList a | creates an array list |
addArrayList | ArrayList a -> a -> () | add an element at the end of the list |
getArrayList | ArrayList a -> Integer -> a | gets an element from the list |
lengthArrayList | ArrayList a -> Integer | returns the current lenght of the list |
freezeArrayList | ArrayList a -> [a] | freezes the list and returns the corresponding immutable list
|
## Browsing Simantics database
----
The functionality we have used so far has been readily available in the console.
There is also functionality that must be imported before using:
~~~
> import "Simantics/Ontologies"
~~~
The string `Simantics/Ontologies` is the name of the *SCL module* that is imported.
We assume that the import command is excuted in the rest of this section.
----
In order to browse the database more conveniently, let's define some helper functions.
~~~
> nameOfResource r = match possibleRelatedValue r L0.HasName with
Just name -> name
Nothing -> "no name"
> printNames (rs :: [Resource]) =
for rs (\r -> print $ nameOfResource r)
> findEntities (p :: Resource -> Boolean) = findRecursively
where
findRecursively r = do
children = concatMap findRecursively $ immediateChildren r
if p r
then [r] + children
else children
immediateChildren s = s # L0.ConsistsOf
> findByType t = findEntities (flip isInstanceOf t)
> aprosOntologies = resource "http://www.apros.fi"
> project = currentProject ()
~~~
Now we can find all connection types defined in the Apros ontologies:
~~~
> printNames $ findByType STR.ConnectionType aprosOntologies
PipelineStructureConnectionType
...
ElectricalNameReferenceConnectionType
~~~
Find all module types:
~~~
> import "http://www.apros.fi/Apros-6.1" as APROS
> printNames $ findByType APROS.AprosModuleType aprosOntologies
~~~
Show all pipes in the workspace with their lengths:
~~~
> import "http://www.apros.fi/Combustion/Configuration-6.0/ModuleTypes"
as MT
> import "http://www.apros.fi/Combustion/Configuration-6.0/Relations"
as REL
> for (findByType MT.PIPE project) (\pipe ->
print $ nameOfResource pipe + " " +
show (relatedValue pipe REL.PI12_LENGTH :: Float)
)
~~~
Find all modules that are not connected anywhere:
~~~
> isUnconnected r = not (existsStatement r STR.IsConnectedTo
|| existsStatement r APROS.AttachedModule)
> printNames $ filter isUnconnected
$ findByType APROS.AprosModule project
~~~
----
This is a list of most important database accessing functions (the
type `Resource` is abbreviated as `Res` in the type signatures):
\setlength\LTleft{-4cm}
resource | String -> Res | the resource with the given URI |
uriOf | Res -> String | the URI of the given resource |
(\#) | Res -> Res -> [Res] | returns all objects for given subject and predicate |
existsStatement | Res -> Res -> Boolean | returns true, if there is a statement with the given subject and predicate |
singleObject | Res -> Res -> Res | returns the unique object with the given subject and predicate |
possibleObject | Res -> Res -> Maybe Res | returns a possible object with the given subject and predicate |
valueOf | Serializable a => Res -> a | reads a value associated with a resource |
relatedValue | Serializable a => Res -> Res -> a | reads a property |
possibleRelatedValue | Serializable a => Res -> Res -> Maybe a | reads a possibly existing property |
inverseOf | Res -> Res | inverse of a relation |
singleTypeOf | Res -> Res -> Res | the unique type of the resource inheriting the given type |
possibleTypeOf | Res -> Res -> Maybe Res | a possible type of the resource inheriting the given type |
isInstanceOf | Res -> Res -> Boolean | tests if a resource is an instance of the given type |
isSubrelationOf | Res -> Res -> Boolean | tests if a resource is a subrelation of a given relation |
isInheritedFrom | Res -> Res -> Boolean | tests if a resource is a subtype of a given type |
claim | Res -> Res -> Res -> () | adds a statement |
deny | Res -> Res -> Res -> () | removes a statement |
claimRelatedValue | Serializable a => Res -> Res -> a -> () | sets the value of a property |
syncRead | (() -> a) -> a | makes a read request |
syncWrite | (() -> a) -> a | makes a write request
|
## Manipulating diagrams
----
Diagrams could be in principle manipulated with the set of primitive
graph functions introduced in the previous section. There is however
some tools available to work with diagrams at a higher level.
Therefore we need for this section the following imports:
~~~
> import "Simantics/Diagram"
> import "http://www.apros.fi/Apros-6.1" as APROS
> import "http://www.apros.fi/Combustion/Configuration-6.0/Relations"
as REL
> import "http://www.apros.fi/Combustion/Diagram-6.0/Symbols"
as SYMBOLS
> import "http://www.apros.fi/Combustion/Diagram-6.0/Relations"
as CPS
~~~
For this section you should also create a model with (default) name Model
and a new diagram to that model with (default) name NewGenericDiagram. If
you want to change these names, you should also change the code
examples accordingly. Populate the diagram with couple of elements and
connect them together.
----
We show first how to read the diagram contents:
~~~
> dia = diagram (model "Model") ["NewGenericDiagram"]
> for (elementsOfR dia) print
(Connection [Terminal "SP_01" #221240, Terminal "MU_01" #218187] [Edge 0 1] (Just "XA_02"), #450761)
(Component #217233 "SP_01" (Position ...) [Property #137577, ...], #450756)
(Component #217663 "MU_01" (Position ...) [Property #144599, ...], #450751)
~~~
The actual result depends on how you have populated your diagram.
The result is not very readable because of all the resource identifiers.
Let's write a pretty-printing function:
~~~
> printElement ((Component t n pos props),_) = do
print $ n + " :: " + nameOfResource t
print $ " " + show pos
for props (\(Property relation value) ->
print $ " " + nameOfResource relation + " " + show value
)
printElement ((Connection nodes _ name),_) = do
print $ "Connection " +
(match name with Just n -> n ; Nothing -> "")
printNode (Terminal element connectionPoint) =
print $ " " + element + " " + nameOfResource connectionPoint
printNode otherwise = ()
for nodes printNode
printElement ((Flag t name _ _ _ _ _ pos _),_) = do
print $ "Flag " + name + " :: " + nameOfResource t
print $ " " + show pos
printElement (el,_) = print el // Use the default printing as a fallback
~~~
Now we get:
~~~>
> for (elementsOfR dia) printElement
Connection XA_02
SP_01 SP_OUTPUT_SIGN_1_1
MU_01 MULTIPLYER_INPUT_SIGN_1_1
SP_01 :: SetpointS
Position 1.0 0.0 0.0 1.0 94.0 115.0
SP_VALUE 0.0
SP_MINMAX_ON false
SP_GRADIENT_UP 60.0
SP_TRACKING_ON false
SP_MIN 0.0
SP_GRADIENT_DOWN -60.0
SP_FAST_MODE_ON true
SP_MAX 100.0
MU_01 :: Gain
Position 1.0 0.0 0.0 1.0 104.0 115.0
MULTIPLYER_BIAS 0.0
MULTIPLYER_GAIN 1.0
MULTIPLYER_OUTPUT 0.0
~~~
----
Next, let's create some new diagrams. We need two helper functions:
~~~
> aprosDiagram modelName diagramName = NewDiagram
(model modelName)
[diagramName]
APROS.Folder
APROS.GenericAprosComposite
> joinMap = createJoinMap ()
~~~
The first function creates a specification for a new diagram:
it describes the model where the diagram is created, the path
to the diagram, the type of folders in the path and finally
the type of the diagram itself. The second definition defines
a `joinMap` that is used for connecting diagrams together (by flags).
Now we can write:
~~~
> newDia = fst $ createDiagramR (aprosDiagram "Model" "TestDiagram1") joinMap []
~~~
This should create a new diagram with name TestDiagram1. The
last parameter is the list of diagram elements. Because the list
is empty, our diagram is also empty.
Easiest way to create some content is to copy it from another diagram:
~~~
> sortEls l = filter (not . isReferring) l + filter isReferring l
where
isReferring (Connection _ _ _) = True
isReferring (SimpleConnection _ _ _ _ _) = True
isReferring (Monitor _ _ _ _) = True
isReferring otherwise = False
> els = sortEls $ map fst $ elementsOfR dia
> createDiagramR (ExistingDiagram newDia) joinMap els
~~~
This replaces the contents of TestDiagram1 by the contents of NewGenericDiagram.
The function `sortEls` is needed to work around a bug in `createDiagramR`
occurring if some element is created after another element that refers to it.
----
Usually we want to modify the contents of the diagram somehow before copying it.
Here are couple of example functions for transforming the elements:
~~~
> mapElementName f (Component t name pos props) =
Component t (f name) pos props
mapElementName f (Connection nodes edges possibleName) =
Connection (map prefixNode nodes) edges (map f possibleName)
where
prefixNode (Terminal name cp) = Terminal (f name) cp
prefixNode n = n
mapElementName f (Flag t name label output external
ioBinding ioTableRowIndex pos refs) =
Flag t (f name) label output external
ioBinding ioTableRowIndex pos refs
mapElementName f el = el // This is not yet a complete definition!
> moveElement delta (Component t name pos props) =
Component t name (move delta pos) props
moveElement delta (Connection nodes edges name) =
Connection (map moveNode nodes) edges name
where
moveNode (RouteLine False pos) = RouteLine False (pos + fst delta)
moveNode (RouteLine True pos) = RouteLine True (pos + snd delta)
moveNode n = n
moveElement delta (Flag t name label output external
ioBinding ioTableRowIndex pos refs) =
Flag t name label output external
ioBinding ioTableRowIndex (move delta pos) refs
moveElement delta el = el // This is not yet a complete definition!
~~~
Now we can move the elements and add a prefix to them:
~~~
> modifiedEls = map (moveElement (20,20))
$ map (mapElementName ((+) "PREFIX_")) els
> createDiagramR (ExistingDiagram newDia) joinMap modifiedEls
~~~
Finally, let's try making some clones of the elements:
~~~
> modifiedEls = [ moveElement (dx,dy) $ mapElementName ((+) prefix) el
| i <- [0..3]
, j <- [0..3]
, dx = 30 * fromInteger i
, dy = 30 * fromInteger j
, prefix = "P_" + show i + "_" + show j + "_"
, el <- els]
> createDiagramR (ExistingDiagram newDia) joinMap modifiedEls
~~~
----
### Exercise
Write a function `createPipeline` that takes as a parameter
a diagram specification and a list of two-dimensional points
and creates a pipeline such that elevations of the points are
the second components of the points in the list, the lengths
of the points are the distances between successive points and
the diagram positions are based on the physical positions.
The following command should work:
~~~
> dia = ExistingDiagram (diagram (model "Model") ["TestDiagram1"])
> createPipeline dia [(1,1), (6,1), (10,5)]
~~~
Hint: The following commands create a pipe and a point that are connected
together with point elevation and pipe lenght specified:
~~~
> els = [
Component SYMBOLS.Point "PO_01" (location 30 30)
[Property REL.PO11_ELEV (toDynamic 10.0)],
Component SYMBOLS.Pipe "PI_01" (location 40 30)
[Property REL.PI12_LENGTH (toDynamic 5.0)],
Connection [Terminal "PO_01" CPS.PointSelf,
Terminal "PI_01" CPS.PI12_CONNECT_POINT_1_1_1]
[Edge 0 1] Nothing
]
> createDiagramR dia joinMap els
~~~
----
### Remark
This is the data type definition of the diagram elements:
~~~
data DiagramElement res =
Component
res // component type
String // name
Position // position
[Property res] // properties
| SimpleConnection String res String res (Maybe String)
| Connection [ConnectionNode res] [Edge] (Maybe String)
| Flag
res
String // name
String // label
Boolean // output
Boolean // external
(Maybe String) // IOTableBinding
(Maybe Integer) // IOTableRowIndex
Position // position
[Dynamic] // references to the joins
| SVG String Position
| Monitor String (Maybe MonitorReference) MonitorVisuals Position
data Position = Position Double Double Double Double Double Double
data Property res = Property res Dynamic
data Edge = Edge Integer Integer
data ConnectionNode res = Terminal String res
| RouteLine
Boolean // is horizontal
Double // position
data Font = Font String Integer Integer
data Alignment = Baseline | Center
| Leading | Trailing
data MonitorReference = MonitorReference String String
data MonitorVisuals = MonitorVisuals (Maybe Font) Double Alignment Alignment
~~~
## Model queries
----
In this section, we make queries and mass modifications to the components
of a model. For preliminaries, you need to run the import command:
~~~
import "Apros/ModelQueries"
~~~
It defines the following functions:
~~~
forAllComponentsIn :: (Resource -> a) -> Resource -> ()
forAllComponents :: (Resource -> a) -> ()
forComponentsIn :: (Resource -> Boolean) -> Resource
-> (Resource -> a) -> ()
forComponents :: (Resource -> Boolean) -> (Resource -> a) -> ()
searchComponents :: (Resource -> Boolean) -> [Resource]
searchComponentsIn :: (Resource -> Boolean) -> Resource -> [Resource]
nameSatisfies :: (String -> Boolean) -> Resource -> Boolean
nameIs name = nameSatisfies (== name)
getName :: Resource -> String
typeIs :: Resource -> Resource -> Boolean
configurationValueSatisfies :: Serializable a => Resource -> (a -> Boolean)
-> Resource -> Boolean
configurationValueIs :: Serializable a => Resource -> a
-> Resource -> Boolean
setConfigurationValue :: Serializable a => Resource -> a -> Resource -> ()
getConfigurationValue :: Serializable a => Resource -> Resource -> a
stateValueSatisfies :: Serializable a => Resource -> (a -> Boolean)
-> (Resource -> Boolean)
stateValueIs :: Serializable a => Resource -> a
-> (Resource -> Boolean)
setStateValue :: Serializable a => Resource -> a
-> (Resource -> ())
getStateValue :: Serializable a => Resource -> (Resource -> a)
(&&&) :: Combinable a => a -> a -> a
instance Combinable (Resource -> Boolean)
instance Combinable (Resource -> ())
printName :: Resource -> ()
row :: [Resource -> String] -> (Resource -> String)
~~~
----
The functions make it possible to define queries to the active
model of the form:
``for all components satisfying a condition execute an action.''
For example:
1) Print the names of all points in the model:
~~~
forComponents
(typeIs MODULETYPES.POINT)
printName
~~~
2) Set area of pipe PIP01 to 0.0033
~~~
forComponents
(nameIs "PIP01")
(setConfigurationValue ATTRIBUTES.PI12_AREA 0.2)
~~~
3) Find all pipes in the active model and set their area to 0.001 m2:
~~~
forComponents
(typeIs MODULETYPES.PIPE)
(setConfigurationValue ATTRIBUTES.PI12_AREA (0.001 :: Float))
~~~
4) Find all pipes with area < 0.0001 m2 in the active model and set their area to 0.001 m2:
~~~
forComponents
(configurationValueSatisfies ATTRIBUTES.PI12_AREA (\(x :: Float) -> x < 0.0001))
(setConfigurationValue ATTRIBUTES.PI12_AREA (0.001 :: Float))
~~~
5) Find all pipes with fluid section WSB and list their boron concentrations:
~~~
forComponents
(configurationValueIs ATTRIBUTES.PO11_SECTION_NAME "WSB")
(print . row [getName, showFloat . getStateValue ATTRIBUTES.PO11_BOR_CONC])
~~~
6) Find all pipes with fluid section WS and set their fluid section to WSB:
~~~
forComponents
(configurationValueIs ATTRIBUTES.PO11_SECTION_NAME "WS")
(setConfigurationValue ATTRIBUTES.PO11_SECTION_NAME "WSB")
~~~
7) Find all pipes in diagram Diagram2 and set their length to 123.45 m:
~~~
forComponentsIn
(typeIs MODULETYPES.PIPE)
(resource "http://Projects/Development%20Project/Model/Configuration/Diagram2")
(setConfigurationValue ATTRIBUTES.PI12_LENGTH (123.45 :: Float))
~~~
----
It is also possible to write the results of a query to a file.
~~~
printingToFile "c:/temp/componentListing.txt" (
forComponents
(configurationValueIs ATTRIBUTES.PO11_SECTION_NAME "WSB")
(print . row [getName, showFloat . getStateValue ATTRIBUTES.PO11_BOR_CONC])
)
~~~
# Further topics
----
If you start to use SCL more frequently and write your own helper functions,
it becomes quickly cumbersome to paste them every time into the console.
You can write a list of commands into a separate file and run it with the command
~~~
> runFromFile "c:/file/path"
~~~
Commands are separated from each other by indentation.
Another way to store function definitions is to write your own modules.
It also frees you from some restrinctions of SCL Console.
You can write the module into a file, say `c:/scl/MyModule.scl`
and import it as:
~~~
> import "file:c:/scl/MyModule"
~~~
What makes working with modules harder than console commands is that
it is not so easy to update a module that you have already imported.
It is however possible by running:
~~~
> reset ""
~~~
that clears the whole SCL environment. After clearing the environment, you
have to reimport all the modules you want to use.
We will improve the situation in next versions of
the SCL environment.
----
There are many language features we have not touched in this tutorial.
Many of them are also in Haskell, so you can familize yourself with them
by reading Haskell tutorials:
* defining new data types,
* defining new type classes and their instances,
* defining binary operators, their associativity and precedence.
SCL supports also monads like Haskell. Only difference is that monadic
syntax uses keyword `mdo` instead of `do`. Althought
the importance of monads is lessen in SCL because of effect types,
they are still a useful concept to know. Monads are particularly
useful for building domain specific languages in SCL.
----
There are also couple of features that we have not described that
are specific to SCL:
* definition of new effect types
* syntax for importing Java code to SCL, for example:
~~~
importJava "java.util.List" where
@JavaName get
(!) :: [a] -> Integer -> a
@JavaName size
length :: [a] -> Integer
subList :: [a] -> Integer -> Integer -> [a]
~~~
* value annotations
~~~
@inline
curry :: ((a, b) -> c) -> a -> b -> c
curry f x y = f (x, y)
@macro
a && b = if a then b else False
~~~