From 39fd9bd29b18a2f7abe62fb13da3359b3618dda7 Mon Sep 17 00:00:00 2001 From: Tuukka Lehtonen Date: Fri, 28 Sep 2018 15:41:34 +0300 Subject: [PATCH] Import org.simantics.scl.tutorial from incubator SVN repo gitlab #138 Change-Id: Ic3590a2992612847294a0db960e485dd3c7bbbf9 (cherry picked from commit e0f2b602b8c67f642b1a88ef588b401f4e64a553) --- bundles/org.simantics.scl.tutorial/.classpath | 7 + bundles/org.simantics.scl.tutorial/.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 6 + .../build.properties | 6 + .../scl/Tutorial/1.01 Console.md | 73 + .../scl/Tutorial/1.02 Language basics.md | 357 ++++ .../scl/Tutorial/1.03 Evaluation semantics.md | 42 + .../scl/Tutorial/1.04 Numbers.md | 32 + .../scl/Tutorial/1.05 Lists.md | 131 ++ .../scl/Tutorial/1.06 Types.md | 178 ++ .../scl/Tutorial/1.07 Booleans.md | 5 + .../scl/Tutorial/1.08 Side effects.md | 34 + .../scl/Tutorial/1.09 Strings.md | 13 + .../Tutorial/1.10 Optional values (Maybe).md | 8 + .../scl/Tutorial/1.11 Functions.md | 6 + .../scl/Tutorial/1.12 Tuples.md | 8 + .../scl/Tutorial/2.02 Model configuration.md | 43 + .../scl/Tutorial/2.03 (Exercise) Pipelines.md | 199 ++ .../scl/Tutorial/2.04 (Exercise) Polyline.md | 67 + .../scl/Tutorial/2.05 Semantic graph.md | 59 + .../2.06 (Exercise) Pipelines again.md | 79 + .../scl/Tutorial/2.07 (Solution) Pipelines.md | 48 + .../scl/Tutorial/2.08 (Solution) Polyline.md | 40 + .../2.09 (Solution) Pipelines again.md | 35 + .../scl/Tutorial/3.01 Simulation sequences.md | 116 ++ .../Tutorial/3.02 (Exercise) Model fitting.md | 125 ++ .../3.03 (Exercise) Controller tuning.md | 46 + .../Tutorial/3.04 (Solution) Model fitting.md | 46 + .../Tutorial/4.01 Other language features.md | 79 + .../scl/Tutorial/X Old tutorial.md | 1698 +++++++++++++++++ bundles/pom.xml | 1 + .../org.simantics.sdk.feature/feature.xml | 7 + 33 files changed, 3629 insertions(+) create mode 100644 bundles/org.simantics.scl.tutorial/.classpath create mode 100644 bundles/org.simantics.scl.tutorial/.project create mode 100644 bundles/org.simantics.scl.tutorial/.settings/org.eclipse.jdt.core.prefs create mode 100644 bundles/org.simantics.scl.tutorial/META-INF/MANIFEST.MF create mode 100644 bundles/org.simantics.scl.tutorial/build.properties create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.01 Console.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.02 Language basics.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.03 Evaluation semantics.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.04 Numbers.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.05 Lists.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.06 Types.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.07 Booleans.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.08 Side effects.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.09 Strings.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.10 Optional values (Maybe).md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.11 Functions.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/1.12 Tuples.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.02 Model configuration.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.03 (Exercise) Pipelines.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.04 (Exercise) Polyline.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.05 Semantic graph.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.06 (Exercise) Pipelines again.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.07 (Solution) Pipelines.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.08 (Solution) Polyline.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/2.09 (Solution) Pipelines again.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/3.01 Simulation sequences.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/3.02 (Exercise) Model fitting.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/3.03 (Exercise) Controller tuning.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/3.04 (Solution) Model fitting.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/4.01 Other language features.md create mode 100644 bundles/org.simantics.scl.tutorial/scl/Tutorial/X Old tutorial.md diff --git a/bundles/org.simantics.scl.tutorial/.classpath b/bundles/org.simantics.scl.tutorial/.classpath new file mode 100644 index 000000000..b1dabee38 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.scl.tutorial/.project b/bundles/org.simantics.scl.tutorial/.project new file mode 100644 index 000000000..16f19de0d --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/.project @@ -0,0 +1,28 @@ + + + org.simantics.scl.tutorial + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/bundles/org.simantics.scl.tutorial/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.scl.tutorial/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..11f6e462d --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/bundles/org.simantics.scl.tutorial/META-INF/MANIFEST.MF b/bundles/org.simantics.scl.tutorial/META-INF/MANIFEST.MF new file mode 100644 index 000000000..f1c87134e --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/META-INF/MANIFEST.MF @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Tutorial +Bundle-SymbolicName: org.simantics.scl.tutorial +Bundle-Version: 1.0.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 diff --git a/bundles/org.simantics.scl.tutorial/build.properties b/bundles/org.simantics.scl.tutorial/build.properties new file mode 100644 index 000000000..0a709036f --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + scl/ +src.includes = scl/ diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.01 Console.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.01 Console.md new file mode 100644 index 000000000..011db6694 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.01 Console.md @@ -0,0 +1,73 @@ +# Console + +The easiest way of getting started with SCL is to use SCL console that is normally included in +Simantics-based products. You can open the console by pressing ALT-SHIFT-q and s or +from menu **Window/Show View/SCL Console**. + +## Executing commands + +SCL console works by executing commands you write into the input box in the bottom of the view. +After the command has been written, it can be executed by pressing ENTER. If the command +contains syntax errors they are written to the console in red text and indication for the +error position. + +Multi-line commands can be written by creating a new line with CTRL-ENTER. +The command history can be browsed with CTRL-UP and CTRL-DOWN. +Also the standard keybindings CTRL-c (Copy), CTRL-v (Paste), CTRL-x (Cut), CTRL-a (Select all) work as expected +both in the input and output area of the console depending on which of them has a focus. +You can also write (or paste) multiple commands at the same time. + +If the command you write into console results as an ordinary value, it is printed +to the console. Here are couple of examples you can try: + +~~~ +> 13 +13 +> 1+2 +3 +> sin 1 +0.8414709848078965 +> "Hello " + "world!" +Hello world! +> [1,3,5] +[1, 3, 5] +~~~ + +The console remembers the variables you declare, but they are forgotten when the +console (or the whole application) is closed. + +~~~ +> x = 35 +> y = 40 +> x + y +75 +> x * y +1400 +~~~ + +If you write a command that prints something as a side-effect, the prints are shown in the console: + +~~~ +> print "Hello" ; print "world!" +Hello +world! +~~~ + +The currently running command can be interrupted with **Interrupt current command** button +in the top right corner of the console. +All commands don't support interruption. +The output area of the console can be cleared with **Clear console** button. + +## Importing modules + +The rightmost button in the console opens a dialog for managing modules that are available +for the console. It shows currently imported modules and contains buttons for importing +modules from different sources. The imported modules are remembered even when the console +is closed if the import is marked persistent. + +The another way to import modules is run import command from the console, for example + + import "Simantics/DB" + +The button with two arrows reloads the modules that are imported to the console. It +is useful if you develop your own module and want to test modified definitions. \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.02 Language basics.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.02 Language basics.md new file mode 100644 index 000000000..82e575c26 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.02 Language basics.md @@ -0,0 +1,357 @@ +# Language basics + +SCL is a functional programming language that is based on [lambda calculus]. +Lambda calculus is a minimal but still Turing complete language with three +constructions: +* *Variables* +* *Function applications* +* *Function definitions* (lambda terms) + +These are also the most central elements of SCL and most of the other constructions are only +syntactic sugar on top of lambda calculus. + +SCL syntax is very close to the syntax of [Haskell] programming language and many tutorials +on Haskell apply also to SCL. The main difference between the languages is the evaluation strategy: +Haskell evaluates terms lazily, SCL strictly. Unlike Haskell, SCL allows side-effects during +function application, but controls them with effect typing. In this respects, it behaves similarly +to [ML] or [OCaml]. + +SCL inherits the following philosophy from Haskell and other programming languages in functional paradigm: +* Avoid stateful programming +* Express mathematical problems in mathematical syntax +* Expressive types prevent runtime errors + +[lambda calculus]: http://en.wikipedia.org/wiki/Lambda_calculus +[Haskell]: https://www.haskell.org/ +[ML]: http://en.wikipedia.org/wiki/ML_(programming_language) +[OCaml]: https://ocaml.org/ + +This section gives a walkthrough for the most importatant constructs of SCL language. + +## Literals + +SCL supports the following types of constant expressions: + +**Integers** + + 3423 + +**Floating point numbers** + + 1.2 + 2.6e-4 + +**String literals** + +SCL supports single-line strings (enclosed in quotes) + + "Single line text" + "Text\nwith\nmultiple lines" + +and multi-line strings (enclosed in triple quotes) + + """Text + with + multiple lines""" + +Single-line strings may contain escaped characters (`\n` line feed, `\t` tabulator, `\r` carriage return, `\uxxxx` unicode character, +`\` the character without the first `\`). + +**Character literals** + + 't' + '\'' + +**Boolean constants** are written as `True` and `False` although they are not technically literals (but constructors). + +## Identifiers + +Variable and constant names start with lower case letter followed by letters, digits, `_` and `'`. +It customary to write multi-word concepts with camel case convention, for example +`printError` or `importA5Model`. + +The following names are reserved and cannot be used as identifiers: +`as`, `by`, `class`, `data`, `deriving`, `do`, `effect`, `else`, `enforce`, `extends`, `forall`, `hiding`, `if`, +`import`, `importJava`, `in`, `include`, `infix`, `infixl`, `infixr`, `instance`, `let`, `match`, `mdo`, `rule`, +`select`, `then`, `transformation`, `type`, `when`, `where`, `with`. + +Identifiers starting with upper case letter are *constructors* and they can be defined only +together with the new data types. Examples: `True`, `False` and `Nothing`. + +## Function applications + +A function is applied by writing the function and its parameters consecutively. + + sin pi + max 2 5 + +A parameter needs to be closed in parenthesis if it is not a variable, literal, or an expression starting +with `if`, `let`, `do`, etc. + + sqrt (x*x + y*y) + atan2 (sin a) (cos a) + +For example + + sqrt x*x + y*y + +is evaluated as + + (sqrt x)*x + y*y + +because the function application has higher precedence than +any binary operator. +Parentheses are also needed around negation + + sin (-1.42) + +because otherwise the expression is tried to compile as + + sin - 1.42 + +causing a compilation error because `sin` and `1.42` are not +compatible for subtraction. + +## Binary operators + +Binary operators are normal functions that are just written between their parameters: + + 1 + 2 + +Each binary operator can be converted into ordinary function by putting parentheses around it + + (+) 1 2 + +Similarly an ordinary function can be converted into binary operator by putting backticks (\`) around it. + + 3.4 `max` 4.5 + +Binary operators have precedences that determines how multiple consecutive binary operators +are compiled. For example + + 1*2+3*4+5*6 + +is evaluated as + + ((1*2) + (3*4)) + (5*6) + +## Variable definitions + +Variables are defined by syntax + + variableName = variableValue + +for example + + g = 9.80665 + +Defined variable values are available in the +consecutive expressions: + + a = 13 + b = 14 + a*b + +## Function definitions + +Functions are defined by writing a function application +in the left-hand side of the equation: + + increaseByOne x = x+1 + +Defined functions are available in the consecutive expressions: + + increaseByOne 13 + +## Conditional expressions + +Conditional expressions are written in the form: + + if then else + +The `else` branch is always mandatory. + + abs x = if x > 0 + then x + else -x + +## Recursion + +A function definition can refer to itself. For example, the Euclidean algorithm for +computing the greatest common divisor of two integers can be written as: + + gcd a b = if a > b + then gcd b a + else if a == 0 + then b + else gcd (b `mod` a) a + +## Local definitions + +Variable and function definitions can be written as a part of larger +expression. There are three language constructs for this purpose: + + let in + + do + + + where + +They are quite similar and +it is usually just a stylistic choice which one to use. + +If you are only defining local variables that are needed in a subexpression +and their computation does not have side-effects (or has only reading effects), +the most natural choice is `let`-construct that allows you to define +variables between `let` and `in` and used the variables in the expression following `in`: + +~~~ +distance (x1,y1) (x2,y2) = let dx = x1-x2 + dy = y1-y2 + in sqrt (dx*dx + dy*dy) +~~~ + +Let-expressions can be freely embedded in other expressions: + +~~~ +""" +Finds a root of f given neg and pos with assumption that + f neg < 0 +and + f pos > 0 +""" +bisectionMethod f neg pos = + let middle = (neg+pos)*0.5 in + if abs (neg-pos) < 1e-9 + then middle + else let middleVal = f middle in + if middleVal < (-1e-9) + then bisectionMethod f middle pos + else if middleVal > 1e-9 + then bisectionMethod f neg middle + else middle +~~~ + +Another construction allowing local variable bindings is `do`. The value of the whole +`do`-expression is determined by the last expression in the `do`-block. This construct +should be used when there are side-effects involved and you want to mix variable bindings +with function applications that ignore their result: + +~~~ +createModel parent name = do + model = newResource () + claim model L0.InstanceOf SIMU.Model + claimRelatedValue model L0.HasName name + claim model L0.PartOf parent + model +~~~ + +The final binding construction is `where` that locates variable definitions after the position the variables are used. It differs from `let` and `do` because +it does not define an expression but is always related to some other definition. +The benefit is that the same `where` -block can be used by multiple +different guarded definitions: + +~~~ +""" +Returns all solutions of the quadratic equation a*x^2 + b*x + c = 0. +""" +solveQuadratic :: Double -> Double -> Double -> [Double] +solveQuadratic a b c + | discriminant < 0 = [] + | discriminant > 0 = let sqrtDisc = sqrt discriminant + in [ (-b-sqrtDisc)/(2*a) + , (-b+sqrtDisc)/(2*a) ] + | otherwise = [ -b / (2*a) ] + where + discriminant = b*b - 4*a*c +~~~ + + +## Functions taking other functions as parameters + +In SCL, functions are ordinary values that can be stored to variables and manipulated. +For example, writing only the function name to the console produces: + + > sin + Double> + +We can define for example a numerical derivative operation + + epsilon = 1e-9 + der f x = (f (x + epsilon) - f (x - epsilon)) / (2*epsilon) + +Now, + + > der sin 0 + 1.0 + > der exp 1 + 2.7182818218562943 + +## Anonymous function definitions + +Functional style of programming favors lots of small functions. +Giving a name for all of them becomes quickly tedious and +therefore functions can be defined also anonymously. For +example + + \x -> x+1 + +defines a function that increases its parameter by one. +Thus + + (\x -> x+1) 13 + +returns `14`. + +Assuming that the function `der` is defined as above + + > der (\x -> x*x) 2 + 4.000000330961484 + +## Partial function application + +It is possible to give a function less parameters that +it accepts: + + > der sin + Double> + +Such a partial application creates a function that expects +the missing parameters: + + > myCos = der sin + > myCos 0 + > myCos pi + -1.000000082740371 + +It is possible to partially apply also binary operators giving +only one of its parameters. Such an application must always +be enclosed in parentheses + + lessThanOne = (< 1) + greaterThanOne = (1 <) + +## Pattern matching + +The constructors are special kind of values and functions +that can be used in the left-hand side of the function and +value definitions. Most common constructors are +the tuple constructors `()`, `(,)`, `(,,)`, ...: + + toPolarCoordinates (x,y) = (sqrt (x*x + y*y), atan2 y x) + toRectangularCoordinates (r,angle) = (r*cos angle, r*sin angle) + +Other constructors are used like functions, but the name +of the constructor is capitalized, for example `Just` and `Nothing`: + + fromMaybe _ (Just v) = v + fromMaybe default Nothing = default + +This example demonstrates also some other features. A function +can be defined with multiple equations and the first matching +equation is used. Also, if some parameter value is not used, +it may be replaced by `_`. + +## Indentation + diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.03 Evaluation semantics.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.03 Evaluation semantics.md new file mode 100644 index 000000000..5c9007f5d --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.03 Evaluation semantics.md @@ -0,0 +1,42 @@ +# Evaluation semantics + +## Single equation + +Assume we have given the following definition: +~~~ +f x = x + 1 +~~~ + +Then the expression `f (f 13)` is evaluated in this way: + +~~~ +f (f 13) +f (13 + 1) because f 13 = 13 + 1 +f 14 +14 + 1 because f 14 = 14 + 1 +15 +~~~ + +## Multiple equations + +If we have a more complicated function definition: +~~~ +fib 1 = 1 +fib 2 = 1 +fib n = fib (n-1) + fib (n-2) +~~~ + +the expression `fib 4` is evaluated as + + ~~~ +fib 4 +fib (4-1) + fib (4-2) because fib 4 = fib (4-1) + fib (4-2) +fib 3 + fib 2 +(fib (3-1) + fib (3-2)) + fib 2 because fib 3 = fib (3-1) + fib (3-2) +fib 2 + fib 1 + fib 2 because fib 3 = fib (3-1) + fib (3-2) +1 + fib 1 + fib 2 because fib 2 = 1 +1 + 1 + fib 2 because fib 1 = 1 +2 + fib 2 +2 + 1 because fib 2 = 1 +3 + ~~~ diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.04 Numbers.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.04 Numbers.md new file mode 100644 index 000000000..85b75d133 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.04 Numbers.md @@ -0,0 +1,32 @@ +# Numbers + +## Numeric types + +The most important numeric type that are used by default are + +::data[Builtin/Integer, Builtin/Double] + +SCL standard library defines also other integer and floating point number types: + +::data[Builtin/Short, Builtin/Long, Builtin/Float, BigInteger/BigInteger] + +## Basic operations on numbers + +::value[Prelude/+,Prelude/-,Prelude/neg,Prelude/*,Prelude//] +::value[Prelude/^,Prelude/sqrt,Prelude/exp,Prelude/log] +::value[Prelude/pi,Prelude/sin,Prelude/cos,Prelude/tan,Prelude/asin,Prelude/acos,Prelude/atan,Prelude/atan2] +::value[Prelude/min,Prelude/max,Prelude/abs,Prelude/floor,Prelude/ceil] +::value[Prelude/div,Prelude/mod] + +## Comparison + +::value[Prelude/==,Prelude/!=] +::value[Prelude/<,Prelude/<=,Prelude/>,Prelude/>=] + +## Conversion between numerical types + +::value[Prelude/fromInteger,Prelude/toInteger,Prelude/fromDouble,Prelude/toDouble] + +## Numerical operations on lists + +::value[Prelude/sum,Prelude/maximum,Prelude/minimum] \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.05 Lists.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.05 Lists.md new file mode 100644 index 000000000..a7a406314 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.05 Lists.md @@ -0,0 +1,131 @@ +# Lists + +## List literals + +A list with a constant length can be written by closing the list +elements in brackets and separating the elements by commas: + + [1,2,3] + [] + +## List comprehension + +Lists can be formed with list comprehension expressions that +resemble set comprehension in mathematics. For example + + [x+1 | x <- lst] + +creates a list of all elements in `lst` increased by one +and + + [x+y | x <- lstX, y <- lstY] + +creates list of all sums of pairs where one element is in `lstX` and one in `lstY`. +Note that the list may contain duplicates. + +It is possible to add also constraints + + [x `div` 2 | x <- lst, x `mod` 2 == 0] + +and definitions + + [x*y | x <- lst, y = x+1] + +Formally, a list comprehension expression is + + [ | , ..., ] + +where list qualifier can be one of the following + +* generator ` <- ` +* guard `` +* definition ` = ` +* `then by ` + +The last type of qualifier can be used to make operations that affects the +whole list, for example + + then drop 3 + +removes the first three elements and + + then sortBy by x + +sorts the elements by `x`. + +## Accessing the list elements + +The most basic way of reading list is to +read it element by element. + +::value[Prelude/!] +::value[Prelude/length] + +## Comparison + +Lists like almost all other types support the generic equality and comparison operations: + +::value[Prelude/==,Prelude/!=] +::value[Prelude/<,Prelude/<=,Prelude/>,Prelude/>=] + +For example `lst == []` tests whether the `lst` is empty. +The comparison of the list is lexicographic. + +## Executing code for each list element + +The only difference between the following iteration functions is +the order of their parameters: + +::value[Prelude/iter, Prelude/for] + +## Concatenating lists + +::value[Prelude/+, Prelude/sum] + +## List transformations + +::value[Prelude/map] +::value[Prelude/filter] +::value[Prelude/join] +::value[Prelude/concatMap] +::value[Prelude/mapMaybe] + +::value[Prelude/zip] +::value[Prelude/zipWith] +::value[Prelude/unzip] + +## Ordering + +The following functions modify the order of the list elements: + +::value[Prelude/sort, Prelude/sortBy, Prelude/sortWith] +::value[Prelude/reverse] + +## Sublists + +The following functions extract some sublist of the given list: + +::value[Prelude/take] +::value[Prelude/drop] +::value[Prelude/sub] + +## Aggregate operations + +Sometimes it is necessary to compute some kind of summary over +all elements of a list. The most useful generic aggregate operation is `foldl`. + +::value[Prelude/foldl] + +It is used to define many more specialized aggreate operations such as + + sum = foldl (+) 0 + product = foldl (*) 1 + maximum = foldl1 max + +There is a variant that traverses the list from right to left: + +::value[Prelude/foldr] + +and a variant that that assumes that the list has at least one element: + +::value[Prelude/foldl1] \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.06 Types.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.06 Types.md new file mode 100644 index 000000000..2b1e69ae9 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.06 Types.md @@ -0,0 +1,178 @@ +# Types + +SCL is statically typed language which means that types of the possible values of all variables are known already +at compile time. The following types (or more exactly, type constructors) have builtin support: + +* `Boolean` +* `Byte`, `Short`, `Integer`, `Long` +* `Float`, `Double` +* `String` +* `[]` +* `()`, `(,)`, `(,,)`, ... +* `Maybe` +* `Vector` + +Other type constructors are either imported from the host language or defined in SCL modules. +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 String` or `Vector Integer`, 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 +... +~~~ + +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. + +## Type annotations + +There are two kind of type annotations in SCL, both use `::` to separate +the value from the type. + +Top-level annotations give a type for top-level definitions and they +can be used only in SCL modules: + + flip :: (a -> b -> c) -> b -> a -> c + flip f x y = f y x + +Inline annotations may be embedded in expressions or patterns: + + execute (print (getVar "PI1#PI12_PRESSURE" :: Double)) + +SCL compiler can usually infer the type of the expressions but there +are couple of reasons to use annotations. They + +* document the definitions +* prevents some typos that would change the type +* restrict the type to be more specific than what the compiler can infer +* prevents the compiler from making a bad guess for the type + +## Type variables + +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`. + +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. For example the type +signatures define the following functions uniquely: + +~~~ +id :: a -> a +swap :: (a,b) -> (b,a) +const :: a -> b -> a +~~~ + +and there are only two possible total functions satisfying the following signature + +~~~ +choose :: a -> a -> a +~~~ + +Type variables may also refer to parametric types, but all useful examples involve type constraints we describe below. + +## Type constraints + +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 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 values of `a` and two `a`:s and returns `a` + +Constrained type variable can be also 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 a` is a superclass of `Ring a`. A function using both operators need to specify only `Ring a` as a constraint: + +~~~ +doubleAndAddOne :: Ring a => a -> a +doubleAndAddOne x = 2*x + 1 +~~~ + +## Effect types + +SCL functions are [referentially transparent]("http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)") +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. + +SCL manages side-effects with *effect types*. +An effectful computation has type ` t`. +The type after angle brackets is the type of the value returned by the computation. +Side-effects that might happen during computation are listed inside of the angle brackets. +Effect types must always occur as a return type of a function. Here are some examples of functions with side-effects: + +~~~ +randomBetween :: Double -> Double -> Double +resourceByUri :: 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 = { f () ; f () } + +(.) :: (b -> c) -> (a -> b) -> (a -> c) +(f . g) x = f (g x) +~~~ \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.07 Booleans.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.07 Booleans.md new file mode 100644 index 000000000..2ef00daaa --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.07 Booleans.md @@ -0,0 +1,5 @@ +# Booleans + +::value[Prelude/&&, Prelude/||, Prelude/not, Prelude/and, Prelude/or, Prelude/all, Prelude/any] + + diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.08 Side effects.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.08 Side effects.md new file mode 100644 index 000000000..801569b3a --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.08 Side effects.md @@ -0,0 +1,34 @@ +# Side effects + +Even if the functional programming style prefers writing +pure functions without side-effects, sometimes side-effects +are the reason for running the function in the first place. + +This section lists the most important functions with +side-effects that are defined in the SCL standard library. + +## Printing + +::value[Prelude/print, Prelude/printString, Prelude/printError, Prelude/printingToFile] + +## References + +::value[Prelude/ref, Prelude/getRef, Prelude/:=] + +## Mutable arrays + +::data[ArrayList/T] +::value[ArrayList/new] +::value[ArrayList/add] +::value[ArrayList/remove] +::value[ArrayList/get] +::value[ArrayList/length] +::value[ArrayList/contains] + +::value[ArrayList/iter, ArrayList/for] +::value[ArrayList/mapInPlace] +::value[ArrayList/popUntilEmpty] + +## Escaping side-effects + +::value[Builtin/runProc] \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.09 Strings.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.09 Strings.md new file mode 100644 index 000000000..224786dae --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.09 Strings.md @@ -0,0 +1,13 @@ +# Strings + +Strings are essentially lists of (Unicode) characters and therefore many functions that operate +on lists can also be used with strings: + +::value[Prelude/+, Prelude/sum, Prelude/length, Prelude/take, Prelude/drop, Prelude/sub] + +Strings have currently their own function for accessing indivial characters +(but in the future they may also support `!` operator). + +::value[Prelude/charAt] + +The following operations are often useful for diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.10 Optional values (Maybe).md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.10 Optional values (Maybe).md new file mode 100644 index 000000000..a71014f7e --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.10 Optional values (Maybe).md @@ -0,0 +1,8 @@ +# Optional values (Maybe) + +The type `Maybe a` can be used in situations where some value can +not be necessarily computed. Its values are either `Nothing` or +`Just v` where the type of `v` is `a`. + +::data[Builtin/Maybe] +::value[Prelude/fromJust, Prelude/fromMaybe, Prelude/execJust, Prelude/filterJust, Prelude/orElse, Prelude/elemMaybe] diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.11 Functions.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.11 Functions.md new file mode 100644 index 000000000..4e5264e05 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.11 Functions.md @@ -0,0 +1,6 @@ +## Functions + +Because functions are values, there are also functions that operate on them + +::value[Prelude/$, Prelude/.] + diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.12 Tuples.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.12 Tuples.md new file mode 100644 index 000000000..d63d7e603 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/1.12 Tuples.md @@ -0,0 +1,8 @@ +# Tuples + +A tuple is fixed length sequence of values that may have different types. +Tuples are constructed by listing their components in parentheses + + (1, "a", 5.4) + +::value[Prelude/fst, Prelude/snd, Prelude/curry, Prelude/uncurry] diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.02 Model configuration.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.02 Model configuration.md new file mode 100644 index 000000000..c881ca5fb --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.02 Model configuration.md @@ -0,0 +1,43 @@ + +# Model configuration + +Apros 6.05 contains a new module `Apros/Legacy` for manipulating the model configuration +with commands that are familiar to Apros 5 developers. Idea of the module is that +all Apros entities are referred by their names in the Apros solver. + +## Updating model configuration + +::value[Apros/Legacy/aadd, Apros/Legacy/aaddNonvisual, Apros/Legacy/amodi, Apros/Legacy/aconnect, Apros/Legacy/aconnectWithName] +::value[Apros/Legacy/ainclude, Apros/Legacy/aexclude, Apros/Legacy/arename] + +## Examples + +The following commands build a point-pipe-point configuration to subprocess MYDIAGRAM. + + import "Apros/Legacy" + aadd "MYDIAGRAM" "Point" "PO01" (50, 50) + amodi "PO01" "PO11_PRESSURE" 0.2 + aexclude "PO01" + + aadd "MYDIAGRAM" "Point" "PO02" (100, 50) + amodi "PO02" "PO11_PRESSURE" 0.1 + aexclude "PO02" + + aadd "MYDIAGRAM" "Pipe" "PIP01" (75, 50) + amodi "PIP01" "PI12_CONNECT_POINT_1" "PO01" + amodi "PIP01" "PI12_CONNECT_POINT_2" "PO02" + +Alternatively `aconnect` can be used to create the same name reference connections: + + aconnect "PO01" "SelfPOINT" "PIP01" "PI12_CONNECT_POINT_1" + aconnect "PO02" "SelfPOINT" "PIP01" "PI12_CONNECT_POINT_2" + +Notice the attribute name "SelfPOINT" which represents the MODNAME terminal of the POINT module. +To make a reference to a module's MODNAME terminal, use "SelfMT" as the attribute name, where +MT is the module type name in uppercase. + +## Making model queries + +The module contains also the basic commands for making model queries: + +::value[Apros/Legacy/ashow, Apros/Legacy/alist, Apros/Legacy/alistOnly, Apros/Legacy/aget, Apros/Legacy/amget, Apros/Legacy/amgetSort] diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.03 (Exercise) Pipelines.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.03 (Exercise) Pipelines.md new file mode 100644 index 000000000..6e1995545 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.03 (Exercise) Pipelines.md @@ -0,0 +1,199 @@ +[pipes.txt]: "http://www.simantics.org/~niemisto/SCL20150513/pipes.txt" + +## Pipelines exercise + +In this exercise, you create a model configuration based on the data in the file [pipes.txt]. + +### Step 1 + +Implement a function + + isNonemptyString :: String -> Boolean + +that returns true, when the string given as a parameter is nonempty: + + > isNonemptyString "" + False + > isNonemptyString "foo" + True + +You need the empty string `""` and the inequality comparison: + +::value[Prelude/!=] + +### Step 2 + +Implement a function + + removeComment :: String -> String + +that removes a comment from a line. A comment starts with `!` and continues +to the end of the line. It should also remove leading and trailing whitespace. For example + + > removeComment "A;B;C ! This is a comment" + "A;B;C" + > removeComment "Hello World!" + "Hello World" + > removeComment "This line contains no comments." + "This line contains no comments." + +The following functions are useful here: + +::value[Prelude/splitString, Prelude/!, Prelude/trim] + +### Step 3 + +New, lets read the file [pipes.txt]. Store it to somewhere in your file system. + +You need to import the module `StringIO`, either using the import dialog or +with the command + + import "StringIO" + +Now, try to read the file using + +::value[StringIO/readLines] + +Remember that `\` is an escape character in SCL. A string containing directory separators +must be written in one of the following forms: + + "c:/temp/pipes.txt" + "c:\\temp\\pipes.txt" + +### Step 4 + +As you see, the file contains empty lines and comments. Implement a function + + loadAndPreprocess :: String -> [String] + +that reads the file, whose name is given as a parameter, removes the comments, empty lines and leading and trailing +whitespace at every line. It returns the preprocessed lines. You need the +functions `isNonemptyString`, `removeComment` you implemented before, +`readLines` and the following functions + +::value[Prelude/map,Prelude/filter] + +### Step 5 + +Create a new SCL module and move your definitions there (if you have not done so already). It +is much easier to continue handling the increasing number of function definitions there. + +### Step 6 + +You may have noticed that the lines in the preprocessed file have entries separated by `;`. +The first entry in each line is either "POINT" or "PIPE". Implement the functions + + isPointLine, isPipeLine :: [String] -> Boolean + +that check whether the first string in a list of strings is "POINT" or "PIPE". +For example + + > isPointLine ["POINT", "1", "2", "3.4", "100", "200"] + +### Step 7 + +Now, add the following definitions to your SCL module: + +~~~ +handlePointEntry :: String -> [String] -> () +handlePointEntry diagram ["POINT", id, pointElevation, x, y] = do + print "Add a point \(id) into the diagram \(diagram) with elevation \(pointElevation) at coordinates \(x),\(y)." + +handlePipeEntry :: String -> Integer -> [String] -> () +handlePipeEntry diagram id ["PIPE", id1, id2, pipeLength] = do + print "Add a pipe \(id) into the diagram \(diagram) connecting the point \(id1) to the point \(id2) with length \(pipeLength)" +~~~ + +Create a function + + readPipesFile :: String -> String -> () + +that is called as + + readPipesFile "diagramName" "fileName" + +It should first read the file and preprocess it using `loadAndPreprocess` you implemented in Step 4. +It should then split each line into entries with `splitString` and `map` +Note that because of the order of the parameters of `splitString` you need either anonymous +functions, a separate funtion definition or +::value[Prelude/flip] + +It should then filter the lines into two lists, one containing all definitions of points +and one all definitions of pipes. Finally, the function should call +`handlePointEntry` for all points using + +::value[Prelude/iter] + +and `handlePipeEntry` for all pipes using (because `handlePipeEntry` has an extra integer parameter) + +::value[Prelude/iterI] + +When finished the function should work like this from the console: + +~~~ +> readPipesFile "X" "c:/temp/pipes.txt" +Add a point 1 into the diagram X with elevation 0 at coordinates 100,100. +Add a point 2 into the diagram X with elevation 1.1 at coordinates 120,100. +Add a point 3 into the diagram X with elevation 1.5 at coordinates 140,100. +Add a point 4 into the diagram X with elevation 2.5 at coordinates 160,100. +Add a pipe 0 into the diagram X connecting the point 1 to the point 2 with length 12 +Add a pipe 1 into the diagram X connecting the point 2 to the point 3 with length 11 +Add a pipe 2 into the diagram X connecting the point 3 to the point 4 with length 13 +~~~ + +### Step 8 + +Reimplement the function `handlePointEntry` so that it creates the points into the diagram with the +specified elevation and diagram coordinates. You need the functions + +::value[Apros/Legacy/aadd, Apros/Legacy/amodi] + +You need also the function + +::value[Prelude/read] + +to convert the diagram coordinates from strings to doubles. +Add some prefix to the point indicies to form the point name (for example +`name = "PO" + id`. The name of the elevation attribute is +`PO11_ELEV`. + +Test your implementation with the example data. + +### Step 9 + +Reimplement the function `handlePipeEntry` so that it creates the pipes into the diagram with +the specified length (`PI12_LENGTH`) and connects it to the specified points +(`PI12_CONNECT_POINT_1` and `PI12_CONNECT_POINT_2`). You may place the pipes to the origin +`(0,0)`. + +Test your implementation again with the example data. + +### Step 10 + +In this final step, fix the coordinates of the pipes so that they are located between the points +they connect. + +You may do this in the following way. In `readPipesFile`, define a function + + coordinates :: String -> Maybe (Double, Double) + +that gives the coordinates of a point when given the index of the point. You may create it +by partially applying the function + +::value[Prelude/index] + +for this. Then, add the new parameter `coordinates` to the function `handlePipeEntry` so that +its signature becomes + + String -> (String -> Maybe (Double, Double)) -> Integer -> [String] -> () + +Now, you may read the coordinates of the points in `handlePipeEntry` like this + + (x1,y1) = fromJust (coordinates id1) + +where + +::value[Prelude/fromJust] + +Use the average of the connected points as the diagram coordinate for the pipe. + \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.04 (Exercise) Polyline.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.04 (Exercise) Polyline.md new file mode 100644 index 000000000..f78059c4a --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.04 (Exercise) Polyline.md @@ -0,0 +1,67 @@ +## Polyline exercise + +### Step 1 + +Implement a function + + addPolyline :: String -> String -> (Double,Double) -> [(Double,Double)] -> () + +that is called as + + addPolyline diagramName name location polylineData + +It creates a polyline `name` to the diagram `diagramName` at `location`. +The polyline should interpolate the points `polylineData` +(assumed in this step to contain at most 30 points). + +You need the following functions: + +::value[Apros/Legacy/aadd,Apros/Legacy/amodi] + +And the following symbol and attributes + + Polyline + FUNCTION_X_COORDINATE(i) + FUNCTION_Y_COORDINATE(i) + FUNCTION_OK(i) + +Test that your function produces a functional model configuration. + +### Step 2 + +Extend your function so that it supports also `polylineList` with more +than 30 points. For example + + addPolyline "Diagram" "PL" (100,100) [(x,sin x) | i <- [0..100], x = 0.1*fromInteger i] + +might produce the following model configuration: + +![](http://www.simantics.org/~niemisto/SCL20150513/Polyline.png) + +You may implement the polyline using the following symbols and attributes + + Polyline + FUNCTION_X_COORDINATE(i) + FUNCTION_Y_COORDINATE(i) + FUNCTION_OK(i) + FUNCTION_INPUT_SIGN + FUNCTION_OUTPUT_SIGN + BranchAnalog + AMUX_INPUT_SIGN + AMUX_OUTPUT_SIGN(i) + LV_CHECKER + LVC_INPUT_SIGN + LVC_LIMIT_VALUE + LVC_OUTPUT_SIGN_1 + ANALOG_SWITCH + SWITCH_CONTROL_S + SWITCH_INP_SIGN_1 + SWITCH_INP_SIGN_2 + SWITCH_OUTP_SIGN + + +You need in addition to `aadd` and `amodi` the function + +::value[Apros/Legacy/aconnect] + +Test that your function produces a functional model configuration. \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.05 Semantic graph.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.05 Semantic graph.md new file mode 100644 index 000000000..edd800d70 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.05 Semantic graph.md @@ -0,0 +1,59 @@ +# Operating on the semantic graph + +All model configurations in Simantics are stored into a semantic graph database. +Therefore it is the most primitive interface for accessing Simantics and in +principle everything is doable with it, although sometimes with a great effort. + +## Understanding semantic graphs + +The best tool for discovering how the model configuration is represented +in the Simantics, is Graph debugger. It can be opened from +**Window/Show View/Other**. Resources can be dragged to the debugger +from the model browser. + +The debugger shows the URI of the resource and all statements related +to it. + +## Accessing resources + +::data[Simantics/DB/Resource] +::value[Simantics/DB/resource,Simantics/DB/relativeResource,Simantics/DB/possibleResource,Simantics/DB/uriOf] +::value[Simantics/DB/currentModel] + +## Navigating + +::value[Simantics/DB/#,Simantics/DB/possibleObject,Simantics/DB/singleObject] +::data[Simantics/DB/Statement] +::value[Simantics/DB/statements,Simantics/DB/singleStatement] + +## Reading literal values + +::value[Simantics/DB/relatedValue, Simantics/DB/nameOf] + +## Type queries + +::value[Simantics/DB/isInstanceOf, Simantics/DB/isSubrelationOf, Simantics/DB/isInheritedFrom] +::value[Simantics/DB/singleTypeOf, Simantics/DB/possibleTypeOf] + +## Transactions + +The graph database can be accessed only in reading or writing transactions. The SCL console +creates the transaction automatically if necessary, but sometimes it is necessary to control +manually where the transactions are started. + +::value[Simantics/DB/syncRead, Simantics/DB/syncWrite, Simantics/DB/asyncRead, Simantics/DB/asyncWrite] + +## Writing + +::value[Simantics/DB/newResource, Simantics/DB/claim, Simantics/DB/claimRelatedValue] +::value[Simantics/DB/deny] + +## Entities + +::class[Simantics/Entity/Entity] + +## Searching + +::value[Simantics/Model/searchByType, Simantics/Model/searchByTypeShallow, Simantics/Model/searchByTypeAndName] +::value[Simantics/Model/searchByTypeAndNameShallow, Simantics/Model/searchByQuery, Simantics/Model/searchByQueryShallow] + Simantics/Model/searchByTypeAndFilter \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.06 (Exercise) Pipelines again.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.06 (Exercise) Pipelines again.md new file mode 100644 index 000000000..5bd43839a --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.06 (Exercise) Pipelines again.md @@ -0,0 +1,79 @@ +## Pipelines again + +In this exercise, we write points and pipes of a +diagram in the same format used earlier for reading. + +### Step 1 + +Use + +::value[Simantics/DB/relativeResource] + +and + +::value[Simantics/DB/currentModel] + +to define a variable `dia` that refers to some composite (configuration element +corresponding to a diagram). Create some content (points and pipes) to +the diagram. + +Print the value to the console and check that it corresponds to the +resource id given by the graph debugger. + +### Step 2 + +Import the ontology `http://www.simantics.org/Layer0-1.1` as `L0`. + +Use + +::value[Simantics/DB/#] + +and `L0.ConsistsOf` to find all children of `dia`. +Print their names using + +::value[Prelude/map, Simantics/DB/nameOf] + +### Step 3 + +Import the ontology `http://www.apros.fi/Combustion/Configuration-6.0` as `Conf`. + +Modify the commands in step 2 to find only the points (`Conf.ModuleTypes.POINT`) of the diagram. + +### Step 4 + +Print the names and elevations of all points in `dia` using the function + +::value[Simantics/DB/relatedValue] + +and the attribute `Conf.Relations.PO11_ELEV`. + +### Step 5 + +Use the relation `http://www.simantics.org/Modeling-1.2/ElementToComponent` +and + +::value[Simantics/DB/singleObject] + +to find the graphical symbol of the point. + +Use the relation `http://www.simantics.org/Diagram-2.2/HasTransform` and + +::value[Simantics/DB/relatedValue] + +to read the transformation of the point. The last two elements of the transformation +are the coordinates of the point. + +### Step 6 + +Give an indices for all points you find for example using + +::value[Prelude/range, Prelude/zip] + +### Step 7 + +Print the data about the points in the format described in +the previous pipelines exercise. + +### Step 8 + +Print the corresponding data about the pipes. \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.07 (Solution) Pipelines.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.07 (Solution) Pipelines.md new file mode 100644 index 000000000..36bbe278e --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.07 (Solution) Pipelines.md @@ -0,0 +1,48 @@ +## Pipelines solution + +~~~ +import "StringIO" +import "Apros/Legacy" + +isNonemptyString :: String -> Boolean +isNonemptyString = (!= "") + +removeComment :: String -> String +removeComment text = (splitString text "!")!0 + +loadAndPreprocess :: String -> [String] +loadAndPreprocess = filter isNonemptyString . map (trim . removeComment) . readLines + +isPointEntry entry = (entry!0) == "POINT" +isPipeEntry entry = (entry!0) == "PIPE" + +handlePointEntry diagram ["POINT", id, pointElevation, x, y] = do + name = "PO" + id + aadd diagram "POINT" name (read x, read y) + amodi name "PO11_ELEV" pointElevation + +handlePipeEntry :: String -> (String -> Maybe (Double, Double)) -> Integer -> [String] -> () +handlePipeEntry diagram coordinates id ["PIPE", id1, id2, pipeLength] = do + name = "PI" + show id + name1 = "PO" + id1 + name2 = "PO" + id2 + (x1,y1) = fromJust (coordinates id1) + (x2,y2) = fromJust (coordinates id2) + aadd diagram "PIPE" name (0.5*(x1+x2), 0.5*(y1+y2)) + amodi name "PI12_CONNECT_POINT_1" name1 + amodi name "PI12_CONNECT_POINT_2" name2 + amodi name "PI12_LENGTH" pipeLength + +processFile :: String -> String -> () +processFile diagram fileName = do + entries = map (flip splitString ";") $ loadAndPreprocess fileName + + // Points + pointEntries = filter isPointEntry entries + iter (handlePointEntry diagram) pointEntries + coordinates = index [(id, (read x, read y)) | [_,id,_,x,y] <- pointEntries] + + // Pipes + pipeEntries = filter isPipeEntry entries + iterI (handlePipeEntry diagram coordinates) pipeEntries +~~~ \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.08 (Solution) Polyline.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.08 (Solution) Polyline.md new file mode 100644 index 000000000..6926c4878 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.08 (Solution) Polyline.md @@ -0,0 +1,40 @@ +## Polyline solution + +~~~ +import "Apros/Legacy" + +connect (ma,ca) (mb,cb) = aconnect ma ca mb cb + +addPolyline :: String -> String -> (Double,Double) -> [(Double,Double)] -> () +addPolyline diagram name position xyPoints = ignore $ addRecursively 1 position xyPoints + where + addRecursively n position xyPoints | length xyPoints <= 30 = + addSmallPolyline n position xyPoints + addRecursively n (x,y) xyPoints = do + swPoint = fst (xyPoints!29) + brName = name + "_BR" + show n + lvName = name + "_LV" + show n + swName = name + "_SW" + show n + aadd diagram "BranchAnalog" brName (x-20, y+5) + aadd diagram "LV_CHECKER" lvName (x, y) + amodi lvName "LVC_LIMIT_VALUE" (fst (xyPoints!29)) + aadd diagram "ANALOG_SWITCH" swName (x+20, y+5) + (in1, out1) = addSmallPolyline n (x, y+10) (take 30 xyPoints) + (in2, out2) = addRecursively (n+1) (x, y+20) (drop 29 xyPoints) + connect (brName, "AMUX_OUTPUT_SIGN(1)") (lvName, "LVC_INPUT_SIGN") + connect (brName, "AMUX_OUTPUT_SIGN(2)") in1 + connect (brName, "AMUX_OUTPUT_SIGN(4)") in2 + connect (lvName, "LVC_OUTPUT_SIGN_1") (swName, "SWITCH_CONTROL_S") + connect out1 (swName, "SWITCH_INP_SIGN_1") + connect out2 (swName, "SWITCH_INP_SIGN_2") + ((brName, "AMUX_INPUT_SIGN"), (swName, "SWITCH_OUTP_SIGN")) + addSmallPolyline n position xyPoints = do + fullName = name + "_" + show n + aadd diagram "Polyline" fullName position + for [1..length xyPoints] $ \i -> do + (x,y) = xyPoints!(i-1) + amodi fullName "FUNCTION_X_COORDINATE(\(i))" x + amodi fullName "FUNCTION_Y_COORDINATE(\(i))" y + amodi fullName "FUNCTION_OK(\(i))" True + ((fullName, "FUNCTION_INPUT_SIGN"), (fullName, "FUNCTION_OUTPUT_SIGN")) +~~~ \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.09 (Solution) Pipelines again.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.09 (Solution) Pipelines again.md new file mode 100644 index 000000000..d614d533c --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/2.09 (Solution) Pipelines again.md @@ -0,0 +1,35 @@ +## Pipelines again solution + +~~~ +import "Simantics/DB" +import "http://www.simantics.org/Layer0-1.1" as L0 +import "http://www.simantics.org/Modeling-1.2" as MOD +import "http://www.simantics.org/Diagram-2.2" as DIA +import "http://www.apros.fi/Combustion/Configuration-6.0" as Conf + +swap (x,y) = (y,x) + +connectPoint1 :: Resource -> Resource +connectPoint1 r = do + c = singleObject r Conf.Relations.PI12_CONNECT_POINT_1_1_1 + singleObject c Conf.Relations.SelfPOINT.Inverse + +connectPoint2 :: Resource -> Resource +connectPoint2 r = do + c = singleObject r Conf.Relations.PI12_CONNECT_POINT_2_1_1 + singleObject c Conf.Relations.SelfPOINT.Inverse + +readDiagram :: Resource -> () +readDiagram dia = do + points = filter (flip isInstanceOf Conf.ModuleTypes.POINT) (dia # L0.ConsistsOf) + indexedPoints = zip [0..length points] points + for indexedPoints $ \(i,r) -> do + trans = relatedValue (singleObject r MOD.ComponentToElement) DIA.HasTransform :: [Double] + print "POINT;\(i);\(relatedValue r Conf.Relations.PO11_ELEV :: Float);\(trans!4);\(trans!5)" + pointByResource = index (map swap indexedPoints) + pipes = filter (flip isInstanceOf Conf.ModuleTypes.PIPE) (dia # L0.ConsistsOf) + for pipes $ \r -> do + id1 = fromJust (pointByResource (connectPoint1 r)) + id2 = fromJust (pointByResource (connectPoint2 r)) + print "PIPE;\(id1);\(id2);\(relatedValue r Conf.Relations.PI12_LENGTH :: Float)" +~~~ \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.01 Simulation sequences.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.01 Simulation sequences.md new file mode 100644 index 000000000..81108c0e7 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.01 Simulation sequences.md @@ -0,0 +1,116 @@ +# Simulation sequences + +## Sequence monad + +::data[Simantics/Sequences/Sequence] + +We call the sequence *instantious*, if its duration is zero, i.e, the sequence finishes immediately after started. + +A cooking recipe is an example of a sequence in the real world. Its return value could be for example the success +indication of the cooking process. + + instance Monad Sequence + +In order to build complex sequences from simple primitives, the sequences implement +[Monad](http://en.wikipedia.org/wiki/Monad_%28functional_programming%29) operations and +its laws. These are + +::value[Prelude/return, Prelude/>>=] + +The sequence `return v` has zero duration, it does not modify the simulator state and returns `v`. The sequence `seqA >>= f` is a sequence that first behaves like `seqA`, and when it has completed and returned a value `resultA`, continues like the sequence `f resultA`. In other words, `(>>=)` concatenates two sequences and the behavior of the latter sequence may depend on the return value of the former sequence. + +::value[Prelude/>>, Prelude/fmap, Prelude/join, Prelude/sequence, Prelude/repeatForever] + +These operations are derived from the primitive monad operations. +The sequence `seqA >> seqB` behaves first like `seqA` and when it has finished it +continues like `seqB`. The sequence `fmap f seq` maps the result of the sequence `seq` by the function +`f`. The sequence `join seq` first behaves like the sequence `seq` and then like the sequence `seq` returned. +The sequence `sequence seqs` executes every sequence in the container `seqs` sequentially. The container can be for example list or `Maybe`. The sequence `repeatForever seq` repeats the sequence `seq` forever, never returning. + +## Actions + + effect Action + +` a` is an instantious operation happening in the simulator and returning a value of type `a`. It can be a pure reading operation, but may also modify the simulator state. The duration of an action is always zero. + +::value[Simantics/Sequences/time, Simantics/Sequences/getVar, Simantics/Sequences/setVar] + +::value[Simantics/Sequences/execute] + +Multiple actions happening at the same time may be written either as separate sequences: + + mdo execute (setVar "SP1#SP_VALUE" 13) + execute (setVar "SP2#SP_VALUE" 14) + +or as one sequence with more complicated action: + + execute do + setVar "SP1#SP_VALUE" 13 + setVar "SP2#SP_VALUE" 14 + +## Controlling time + +::value[Simantics/Sequences/waitStep] + +::value[Simantics/Sequences/waitUntil, Simantics/Sequences/wait] + +::value[Simantics/Sequences/waitCondition] + +## Parallel execution + +::value[Simantics/Sequences/fork, Simantics/Sequences/halt, Simantics/Sequences/stop] + +## Semantics + +Although the simulation sequences support threading, its semantics is deterministic. This is ensured by the following equivalences: + + halt >> seqA = halt + stop >> seqA = stop + fork (execute actionA >> seqA) >> seqB = execute actionA >> fork seqA >> seqB + fork (waitStep >> seqA) >> execute actionB >> seqB = execute actionB >> fork seqA >> seqB + fork (waitStep >> seqA) >> waitStep >> seqB = waitStep >> fork seqA >> seqB + fork halt >> seqB = seqB + fork seqA >> halt = seqA + fork stop >> seqB = stop + fork (waitStep >> seqA) >> stop = stop + +## Using the sequences with Apros + +In order to run the sequence in Apros, function + +::value[Apros/Sequences/runSequence] + +has been defined. It starts automatically starts simulation, if it is not yet running. When all simulation threads are halted or some thread calls `stop` the simulation is stopped. The sequence can also aborted by aborting the SCL-command (red box in the upper right corner of the console). + + import "Apros/Sequences" + runSequence mdo + fork $ repeatForever mdo + waitCondition (getVar "TA01#TA11_LIQ_LEVEL" >= 3.0) + execute (setVar "BP01#PU11_SPEED_SET_POINT" 0.0) + wait 1 + fork $ repeatForever mdo + waitCondition (getVar "TA01#TA11_LIQ_LEVEL" <= 2.0) + execute (setVar "BP01#PU11_SPEED_SET_POINT" 100.0) + wait 1 + +## Examples + +Check that pressure of the point stays below a certain value: + + fork mdo waitCondition (getVar "POINT1#PO11_PRESSURE" > 120.0) + execute (print "Error! Error!") + stop + +Check that the valve is closed 10 seconds after the operator presses the button: + + fork $ repeatForever mdo + waitCondition (getVar "BUTTON#BINARY_VALUE") + fork mdo + wait 10 + valvePos <- execute (getVar "VALVE#VA11_POSITION") + if valvePos == 0 + then return () // OK + else mdo + execute (print "Error! Error!") + stop + diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.02 (Exercise) Model fitting.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.02 (Exercise) Model fitting.md new file mode 100644 index 000000000..d7602fd76 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.02 (Exercise) Model fitting.md @@ -0,0 +1,125 @@ +## Model fitting exercise + +### Step 1 + +Create a diagram named X and run the following commands to create a simple model configuration: + +~~~ +aadd "X" "POINT" "PO1" (100,110) +aadd "X" "POINT" "PO2" (140,110) +aadd "X" "POINT" "PO3" (180,100) +aadd "X" "POINT" "PO4" (180,120) +aadd "X" "PIPE" "PI1" (120,110) +aadd "X" "PIPE" "PI2" (160,100) +aadd "X" "PIPE" "PI3" (160,120) +amodi "PI1" "PI12_CONNECT_POINT_1" "PO1" +amodi "PI1" "PI12_CONNECT_POINT_2" "PO2" +amodi "PI2" "PI12_CONNECT_POINT_1" "PO2" +amodi "PI2" "PI12_CONNECT_POINT_2" "PO3" +amodi "PI3" "PI12_CONNECT_POINT_1" "PO2" +amodi "PI3" "PI12_CONNECT_POINT_2" "PO4" +aexclude "PO1" +aexclude "PO3" +aexclude "PO4" +amodi "PI1" "PI12_AREA" 0.06 +amodi "PI2" "PI12_AREA" 0.03 +amodi "PI3" "PI12_AREA" 0.03 +amodi "ECCO" "MAXIMUM_TIME_STEP" 0.05 +amodi "ECCO" "CURRENT_TIME_STEP" 0.05 +amodi "SPEED" "SC_SPEED" 1000.0 +~~~ + +Save the initial condition and set the simulation time to zero. + +### Step 2 + +Run the following commands to print how mass flow of the +pipe PI1 behaves in a simple transient. + +~~~ +loadInitialCondition (syncRead $ \_ -> fromResource $ relativeResource currentModel "/Initial%20Condition") +runSequence mdo + execute (setVar "PO1#PO11_PRESSURE" 1.1) + fork (wait 1 >> stop) + fork $ repeatForever mdo + wait 0.05 + execute do + massFlow = getVar "PI1#PI12_MIX_MASS_FLOW" :: Double + print massFlow +~~~ + +### Step 3 + +Modify the above commands so that they print the index of the time step +and variables PI1#PI12_MIX_MASS_FLOW, PI2#PI12_MIX_MASS_FLOW and PI3#PI12_MIX_MASS_FLOW. +The commands should produce something like: + +~~~ +0 3.4978646673069216 1.728445205173922 1.728445205173922 +1 143.55517382976987 71.78198788751189 71.78198788751189 +2 261.95050765748715 130.97625819564252 130.97625819564252 +3 354.3858379588669 177.19356818048865 177.19356818048865 +... +~~~ + +Use the following functions to maintain the counter +(although it is possible to compute it also from the current simulation time). + +::value[Prelude/ref, Prelude/getRef, Prelude/:=] + +### Step 4 + +Modify the commands so that they compute the squared sum of errors from the "measurements" below: + +~~~ +measurements = [ + [0.05, 3.4977860762417605, 1.7283740169550754, 1.7284367287300313], + [0.1, 134.88381983045093, 64.08353845423913, 70.80714606160574], + [0.15, 231.9062548984293, 105.65792385470164, 126.24691709056889], + [0.2, 296.4661748497294, 130.5064306373064, 165.95821168905044], + [0.25, 336.98201115920403, 144.68635329210002, 192.29464808262938], + [0.3, 361.56600347367186, 152.64673170010516, 208.9187479324867], + [0.35, 376.0973084183755, 157.07188101607773, 219.0252113124207], + [0.4, 384.690502383305, 159.57004084578205, 225.12040809039868], + [0.45, 389.742925383871, 160.98719663407357, 228.75573737254442], + [0.5, 392.7040735842458, 161.79511170873698, 230.90898615966157], + [0.55, 394.4365833534546, 162.2577698637119, 232.17883313521224], + [0.6, 395.36535200557034, 162.50160633728285, 232.86375430699712], + [0.65, 395.93996807514526, 162.65081127152007, 233.28915819466746], + [0.7, 396.2954901434386, 162.74229076331505, 233.55318938336018], + [0.75, 396.51546801100426, 162.79847242004152, 233.71697886953123], + [0.8, 396.65158867682203, 162.83302418223676, 233.81854447407886], + [0.85, 396.7358254901475, 162.85429827014457, 233.881506261103], + [0.9, 396.7879581657287, 162.8674097925786, 233.92052799957008], + [0.95, 396.8202241096274, 162.87549715428165, 233.94470806962704], + [1.0, 396.8401951361238, 162.88048891915273, 233.95968928259308]] +~~~ + +The columns of the data are, the simulation time +PI1#PI12_MIX_MASS_FLOW, PI2#PI12_MIX_MASS_FLOW and PI3#PI12_MIX_MASS_FLOW. + +### Step 5 + +Now create a function `objFun :: [Double] -> Double` that is called as + + objFun [1.0,1.1,1.2] + +It should run the same commands as in previous steps and return the +squared sum of errors, but additionally set +PI1#PI12_LOSS_COEFF, PI2#PI12_LOSS_COEFF and PI3#PI12_LOSS_COEFF +to the given values. + +It is good idea to also print the parameter values +at the beginning of the function (and remove all other printing). + +### Step 6 + +Use the function + +::value[Simantics/Newuoa/newuoa] + +to fit the loss coefficients to the measurements. +Try first with small number of function evaluations, +because the current implementation does not allow interrupting +the optimization. You may try `rhobeg=1` and `rhoend=1e-4` +and initial guess `[1,1,1]`. diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.03 (Exercise) Controller tuning.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.03 (Exercise) Controller tuning.md new file mode 100644 index 000000000..abdfa8a8d --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.03 (Exercise) Controller tuning.md @@ -0,0 +1,46 @@ +## Controller tuning exercise + +### Step 1 + +Load the model [ControllerTuning.apros](http://www.simantics.org/~niemisto/SCL20150513/ControllerTuning.apros). + +Run a sequence from SCL console that first sets the value of the set point (`SP01#SP_VALUE`) to 110, +then waits 10 seconds and stops. + +::value[Apros/Sequences/runSequence,Simantics/Sequences/execute,Simantics/Sequences/setVar,Simantics/Sequences/wait,Simantics/Sequences/stop] + +Open the chart and examine the results. + +### Step 2 + +Put your sequence inside a function that takes the controller parameters +`PIC01#PI_GAIN` and `PIC01#PI_INTEGRATION_TIME` as a parameter and sets +them at the same time with the set point. It should also load the IC +before simulation with command + + loadInitialCondition (syncRead $ \_ -> fromResource $ relativeResource currentModel "/Initial%20Condition") + +### Step 3 + +Create a separate simulation thread that computes the last time the flow speed +`XA01#ANALOG_VALUE` is within 1 from the set point value 110. Your function +should return that value. The following functions are useful for that: + +::value[Simantics/Sequences/getVar] +::value[Simantics/Sequences/fork] +::value[Prelude/repeatForever] + +### Step 4 + +Try the optimization routine + +::value[Simantics/Newuoa/bobyqa] + +with function + + f [x,y] = let dx=x-1 ; dy=y-2 in dx*dx + dy*dy + +### Step 5 + +Optimize the controller parameters using the objective function you created in step 3. + diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.04 (Solution) Model fitting.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.04 (Solution) Model fitting.md new file mode 100644 index 000000000..01a762b2c --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/3.04 (Solution) Model fitting.md @@ -0,0 +1,46 @@ +## Model fitting solution + +The original loss coefficints are (although the optimizer will not find exact values) + + amodi "PI1" "PI12_LOSS_COEFF" 1.2 + amodi "PI2" "PI12_LOSS_COEFF" 4.6 + amodi "PI3" "PI12_LOSS_COEFF" 2.2 + +Here is one possible solution: + +~~~ +import "Apros/Sequences" +import "Apros/InitialCondition" +import "Simantics/DB" +import "Simantics/Model" + +objFun (p :: [Double]) = do + print p + loadInitialCondition (syncRead $ \_ -> fromResource $ relativeResource currentModel "/Initial%20Condition") + iRef = ref 0 + sqSum = ref (0 :: Double) + runSequence mdo + execute do + setVar "PO1#PO11_PRESSURE" 1.1 + setVar "PI1#PI12_LOSS_COEFF" (p!0) + setVar "PI2#PI12_LOSS_COEFF" (p!1) + setVar "PI3#PI12_LOSS_COEFF" (p!2) + fork (wait 1 >> stop) + fork $ repeatForever mdo + wait 0.05 + execute do + i = getRef iRef + iRef := i+1 + row = measurements!i + a = getVar "PI1#PI12_MIX_MASS_FLOW" - row!1 :: Double + b = getVar "PI2#PI12_MIX_MASS_FLOW" - row!2 :: Double + c = getVar "PI3#PI12_MIX_MASS_FLOW" - row!3 :: Double + sqSum := getRef sqSum + a*a + b*b + c*c + getRef sqSum +~~~ + +with this definition call + + newuoa 1.0 1e-4 100 objFun [1,1,1] + +from console. \ No newline at end of file diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/4.01 Other language features.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/4.01 Other language features.md new file mode 100644 index 000000000..39ddf64ae --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/4.01 Other language features.md @@ -0,0 +1,79 @@ +## Importing functionality from Java + +Java interfaces and classes can be imported from Java by declaring them inside `importJava` block: + +~~~ +importJava "java.util.regex.Pattern" where + data Pattern + +importJava "java.util.List" where + data List a +~~~ + +Java methods, constructors and fields can be similarly imported by giving +their type annotations in `importJava` block: + +~~~ +importJava "java.util.regex.Pattern.compile" where + @JavaName compile + compilePattern :: String -> Pattern + + @JavaName matcher + createMatcher :: Pattern -> String -> Matcher + +importJava "java.util.regex.Matcher" where + data Matcher + + @JavaName matches + matcherMatches :: Matcher -> Boolean + +matches : Pattern -> String -> Boolean +matches pattern text = do + matcherMatches (createMatcher pattern text) +~~~ + +Another example: + +~~~ +importJava "java.util.ArrayList" where + @JavaName "" + createArrayList :: () -> List a + + @JavaName "" + createArrayListWithCapacity :: Integer -> List a + + @JavaName size + sizeList :: List a -> Integer + + @JavaName get + getList :: List a -> Integer -> a + + @JavaName set + setList :: List a -> Integer -> a -> () + + @JavaName add + addList :: List a -> a -> Boolean +~~~ + +Java constructor is referred with `""`. If Java method name and SCL name matches the annotation `@JavaName` +can be left out. Java import mechanism tries to be quite flexible. It provides some arguments based on the effects +the function has. It also ignores the return value of the Java method if the return type is `()` in SCL. + +A major functionality currently still missing is the ability to create new implementations of existing Java interfaces +in SCL code or extend an existing class. This can be worked around currently by writing new implementations in Java. + +## Relational sublanguage + +* Select, when, enforce +* Transformations + +## Other language features + +* Defining data types +* Defining type classes +* Defining effects +* Restricted imports +* Documentation strings +* Private definitions +* Binary operator precedence + diff --git a/bundles/org.simantics.scl.tutorial/scl/Tutorial/X Old tutorial.md b/bundles/org.simantics.scl.tutorial/scl/Tutorial/X Old tutorial.md new file mode 100644 index 000000000..68baf4527 --- /dev/null +++ b/bundles/org.simantics.scl.tutorial/scl/Tutorial/X Old tutorial.md @@ -0,0 +1,1698 @@ + + +# 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 + ~~~ + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 88535ff46..ae17e0215 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -193,6 +193,7 @@ org.simantics.scl.reflection org.simantics.scl.rest org.simantics.scl.runtime + org.simantics.scl.tutorial org.simantics.scl.ui org.simantics.selectionview org.simantics.selectionview.ontology diff --git a/features/org.simantics.sdk.feature/feature.xml b/features/org.simantics.sdk.feature/feature.xml index 090d11f21..4bc5e4bd5 100644 --- a/features/org.simantics.sdk.feature/feature.xml +++ b/features/org.simantics.sdk.feature/feature.xml @@ -350,4 +350,11 @@ version="0.0.0" unpack="false"/> + + -- 2.47.1