]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.tutorial/scl/Tutorial/1.06 Types.md
Import org.simantics.scl.tutorial from incubator SVN repo
[simantics/platform.git] / bundles / org.simantics.scl.tutorial / scl / Tutorial / 1.06 Types.md
1 # Types\r
2 \r
3 SCL is statically typed language which means that types of the possible values of all variables are known already\r
4 at compile time. The following types (or more exactly, type constructors) have builtin support:\r
5 \r
6 * `Boolean`\r
7 * `Byte`, `Short`, `Integer`, `Long`\r
8 * `Float`, `Double`\r
9 * `String`\r
10 * `[]`\r
11 * `()`, `(,)`, `(,,)`, ...\r
12 * `Maybe`\r
13 * `Vector`\r
14 \r
15 Other type constructors are either imported from the host language or defined in SCL modules.\r
16 Except for the couple of special cases in the previous list, the names of all type constructors are capitalized.\r
17 \r
18 Some type constructors are parametric (compare to generics in Java or templates in C++).\r
19 For example, the list type constructor `[]` has one parameter: the type of the list elements.\r
20 Thus `[Integer]` is the type of the integer lists and `[String]` is the type of string lists.\r
21 `[[Integer]]` is the type of the lists of integer lists. Parameters are usually\r
22 written after the parametric type constructor: for example `Maybe String` or `Vector Integer`, but\r
23 some of the builtin type constructors can be written in a special way in order to make the type\r
24 expressions more readable:\r
25 \r
26 ~~~\r
27 [a] = [] a\r
28 (a,b) = (,) a b\r
29 (a,b,c) = (,,) a b c\r
30 ...\r
31 ~~~\r
32 \r
33 Particularly important type constructor is `->` for building function types. For example, the type\r
34 of the function computing the length of a string is `String -> Integer`: the function takes a string\r
35 as a parameter and returns an integer.\r
36 \r
37 Types of the functions taking multiple parameters are written by composing function types. For example,\r
38 the type of a function taking nth element of a string list is `[String] -> Integer -> String`. The function\r
39 takes a string list and an integer as a parameter and returns a string. Function type operator `->`\r
40 is right associative thus the previous type is equivalent to `[String] -> (Integer -> String)`.\r
41 Thus the type expression can be read as well as a type of functions taking a string list and returning another\r
42 function from integers and strings.\r
43 \r
44 `(a,b)` is the type of pairs where the first component of the pair has type `a` and the second component has type `b`.\r
45 Tuple types `(a,b,c)`, `(a,b,c,d)` etc. are defined similarly. `Maybe a` is the type of optional values.\r
46 \r
47 ## Type annotations\r
48 \r
49 There are two kind of type annotations in SCL, both use `::` to separate\r
50 the value from the type.\r
51 \r
52 Top-level annotations give a type for top-level definitions and they\r
53 can be used only in SCL modules:\r
54 \r
55     flip :: (a -> b -> <e> c) -> b -> a -> <e> c\r
56     flip f x y =  f y x\r
57 \r
58 Inline annotations may be embedded in expressions or patterns:\r
59 \r
60     execute (print (getVar "PI1#PI12_PRESSURE" :: Double))\r
61 \r
62 SCL compiler can usually infer the type of the expressions but there\r
63 are couple of reasons to use annotations. They\r
64 \r
65 * document the definitions\r
66 * prevents some typos that would change the type\r
67 * restrict the type to be more specific than what the compiler can infer\r
68 * prevents the compiler from making a bad guess for the type\r
69 \r
70 ## Type variables\r
71 \r
72 Many functions can be defined so that they do not need to know the exact types they are operating with.\r
73 Such unknown types can be filled in type expressions by type variables: for example the function we\r
74 discussed earlier that took nth element of a string list does not need the information that list elements\r
75 are strings in its implementation and so it can be given a more generic type `[a] -> Integer -> a`,\r
76 i.e a function taking a list of `a`:s and an integer as a parameter and returning a single `a`.\r
77 \r
78 Function types with type variables tell quite much about the function assuming it is total,\r
79 i.e does not hang or throw a runtime exception with any parameters. For example the type\r
80 signatures define the following functions uniquely:\r
81 \r
82 ~~~\r
83 id :: a -> a\r
84 swap :: (a,b) -> (b,a)\r
85 const :: a -> b -> a\r
86 ~~~\r
87 \r
88 and there are only two possible total functions satisfying the following signature\r
89 \r
90 ~~~\r
91 choose :: a -> a -> a\r
92 ~~~\r
93 \r
94 Type variables may also refer to parametric types, but all useful examples involve type constraints we describe below.\r
95 \r
96 ## Type constraints\r
97 \r
98 SCL does not support function overloading at least in the way Java and C++ support it.\r
99 This means that every function has a unique type signature. Now consider the addition function `(+)`.\r
100 We could defined its signature for example as `Integer -> Integer -> Integer`,\r
101 but then we would need some other name for the sum of doubles `Double -> Double -> Double`\r
102 or strings `String -> String -> String`. It would seem like the right signature would be\r
103 `a -> a -> a`, but that is not satisfactory either, because then the function had to\r
104 somehow support all possible types. \r
105 \r
106 The solution to the problem is to constraint the set of allowed types for the type variable.\r
107 Such a constrained type is written in the case of addition function as \r
108 `Additive a => a -> a -> a`. The constraint `Additive a` is composed of a type class\r
109 `Additive` followed by parameters of the constraint. The type can be understood in\r
110 two different ways. A logical reading is\r
111 > for any additive type `a`, the function takes two `a`:s as parameters and returns `a`\r
112 and operational reading that gives a good intuition what is happening in runtime\r
113 > the function takes a parameter `Additive a` that describes a set of methods that\r
114 > can be used to operate values of `a` and two `a`:s and returns `a`\r
115 \r
116 Constrained type variable can be also parametric. For example, let's say we want\r
117 to define a function `getNth` that takes nth element of a list but also works with arrays.\r
118 The signature would then be\r
119 \r
120 ~~~\r
121 getNth :: Sequence s => s a -> Integer -> a\r
122 ~~~\r
123 \r
124 Type classes form a hierarchy: each class may have any number of superclasses.\r
125 Only the most specific type class is needed in the constraints. For example,\r
126 addition and multiplication have the following signatures:\r
127 \r
128 ~~~\r
129 (+) :: Additive a => a -> a -> a\r
130 (*) :: Ring a => a -> a -> a\r
131 ~~~\r
132 \r
133 and `Additive a` is a superclass of `Ring a`. A function using both operators need to specify only `Ring a` as a constraint:\r
134 \r
135 ~~~\r
136 doubleAndAddOne :: Ring a => a -> a\r
137 doubleAndAddOne x = 2*x + 1\r
138 ~~~\r
139 \r
140 ## Effect types\r
141 \r
142 SCL functions are [referentially transparent]("http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)")\r
143 which means that they are like mathematical functions always returning the same value for the same\r
144 parameters and not causing any observable side-effects. This seems extremely restrictive\r
145 because most of the programs are written in order to generate some kind of side-effects and even\r
146 completely mathematical algorithms involve functions that are not referentially transparent such as\r
147 generation of random numbers.\r
148 \r
149 SCL manages side-effects with *effect types*.\r
150 An effectful computation has type `<effects> t`.\r
151 The type after angle brackets is the type of the value returned by the computation.\r
152 Side-effects that might happen during computation are listed inside of the angle brackets.\r
153 Effect types must always occur as a return type of a function. Here are some examples of functions with side-effects:\r
154 \r
155 ~~~\r
156 randomBetween :: Double -> Double -> <Nondet> Double\r
157 resourceByUri :: String -> <ReadGraph> Resource\r
158 claim :: Resource -> Resource -> Resource -> <WriteGraph> ()\r
159 randomLiteral :: Double -> Double -> <Nondet,WriteGraph> Resource\r
160 ~~~\r
161 \r
162 Effect types are much like type constraints: you can mostly ignore them when using functions.\r
163 All effects you use are automatically collected and added to the type signature of the defined\r
164 function (or checked against type annotation if provided).\r
165 \r
166 Like type classes, effects form a hierarchy. For example `WriteGraph` inherits `ReadGraph`\r
167 and a function both reading and writing to graph is annotated only with `<WriteGraph>` effect.\r
168 \r
169 Like types, effects can also be abstracted with effect variables. This is important when\r
170 defining generic functions that manipulate possibly effectful functions:\r
171 \r
172 ~~~\r
173 executeTwice :: (() -> <e> ()) -> <e> ()\r
174 executeTwice f = { f () ; f () }\r
175 \r
176 (.) :: (b -> <e2> c) -> (a -> <e1> b) -> (a -> <e1,e2> c)\r
177 (f . g) x = f (g x)\r
178 ~~~