# 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 ~~~