File: advanced_features.md

package info (click to toggle)
surgescript 0.5.4.4-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,876 kB
  • sloc: ansic: 13,674; makefile: 16
file content (308 lines) | stat: -rw-r--r-- 8,564 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
Advanced features
=================

This section describes advanced features of SurgeScript.

Lookup operator
---------------

Some programming languages, such as C++, have a feature called *operator overloading*. It's a *syntactic sugar* that allows the programmer to attribute custom implementations to different operators.

In SurgeScript, the `[]` operator (also called the *lookup operator*), used by [Arrays](/reference/array) and [Dictionaries](/reference/dictionary), is used to **get** and **set** values from/to the data structure. In fact, the `[]` operator can be used with any object. It is necessary to define, in your object, functions `get()` and `set()` with the following signature:

```
fun get(key)
{
    // custom implementation
}

fun set(key, value)
{
    // custom implementation
}
```

Given an object `obj`, the expression `x = obj[key]` is equivalent to `x = obj.get(key)`. Similarly, `obj[key] = value` is equivalent to `obj.set(key, value)`.

Function objects
----------------

In SurgeScript, objects can be made to behave like functions. We call these objects *function objects* (or *functors*). To make an object behave like a function, you have to overload the `()` operator (also known as the *function operator*). This is done by defining function `call()` in your object:

```
fun call()
{
    // custom implementation
}
```

Function `call()` may take any number of parameters. Given an object `f`, the expression `y = f(x)` is equivalent to `y = f.call(x)`. Notice that, since `f` is an object, you may exchange its implementation during runtime.

Assertions
----------

The `assert(condition)` statement specifies a `condition` that you expect to be true at a certain point in your code. If that condition turns out to be false, the code will be interrupted with an *assertion failed* error. Example:

```
assert(name == "Surge"); // will crash if name isn't "Surge"
```

Chaining
--------

In SurgeScript, it's possible to configure objects in an elegant way using a technique called *chaining*. Consider the object below - it simply displays a message at regular intervals:

```
object "Parrot"
{
    message = "I am a Parrot";

    state "main"
    {
        if(timeout(1.0))
            state = "print";
    }

    state "print"
    {
        Console.print(message);
        state = "main";
    }

    // Note that this function returns
    // this, i.e., the object itself.
    fun setMessage(newMessage)
    {
        message = newMessage;
        return this;
    }
}
```

Suppose that, in your Application, you would like to spawn that object and modify its message. One way of doing it would be making its internal variable `public` and changing its contents in the [constructor function](/tutorials/functions) of your Application. A more concise and elegant way of doing it would be calling function `setMessage()` just after you spawn the object:

```
object "Application"
{
    parrot = spawn("Parrot").setMessage("Hello!");

    state "main"
    {
    }
}
```

Observe that the function we have defined does two things:

* It modifies the internals of the object in some way
* It always returns `this` (that is, the object itself)

We call that function a *chainable function*. You may call such a function from your Application, just after `spawn()`, and you'll still have a reference to the spawned object. Moreover, since chainable functions always return `this`, you may chain multiple function calls into a single statement, making your code concise and your statement descriptive. Example:

```
parrot = spawn("Parrot").setMessage("Hello!").setInterval(2.0);
```

Factory
-------

In SurgeScript, a factory is a functor that spawns an object for you. The object can be spawned and configured in a single call. In the example below, factory `Greeter` spawns and configures `Greeting` objects. We annotate the factory with `@Package`, so it can be imported anywhere in the code.

To the end-user, calling `Greeter()` is simpler than manually spawning and configuring a `Greeting` every time it is needed.

```
// Factory example
using Greeter; // import the factory

object "Application"
{
    state "main"
    {
		// This will print:
		// Hello, alex!
		g = Greeter("alex");
		g.greet();
        exit();
    }
}

// File: greeting.ss
object "Greeting"
{
	public name = "anon";

	fun greet()
	{
		Console.print("Hello, " + name + "!");
	}
}

@Package
object "Greeter"
{
	// Greeter is a factory. It spawns and configures
	// a Greeting object for you. As it is a package,
	// it can be imported and used anywhere.
	fun call(name)
	{
		g = spawn("Greeting");
		g.name = name;
		return g;
	}
}
```

In the example above, objects spawned by the factory will be children of the factory. If you need the parent of the spawned object to be the caller, simply write `g = caller.spawn("Greeter")`. Know that `caller` points to the object that called the function (or `null` if not applicable).

Iterators
---------

As seen in the [loops](/tutorials/loops#foreach) section, the foreach loop may be used to iterate through an iterable collection. In SurgeScript, an iterable collection is an object that implements the iterator protocol described below.

You may implement your own iterable collections by tagging them as `"iterable"` and implementing function `iterator()`. If you have ever used Java, you'll find this to be familiar.

```
// Iterable collections are tagged "iterable"
// and implement function iterator()
object "MyCollection" is "iterable"
{
    fun iterator()
    {
        // function iterator() takes no arguments and 
        // returns a new iterator object
    }
}
```

For each iterable collection you define, you must define its iterator object. The iterator object must be tagged `"iterator"` and implement functions `next()` and `hasNext()` (both take no arguments):

```
// Iterators are tagged "iterator" and
// implement functions next() and hasNext()
object "MyIterator" is "iterator"
{
    fun next()
    {
        // returns the next element of the collection
        // and advances the iteration pointer
        // the iterable collection is usually the parent
        // object, i.e., collection = parent
    }

    function hasNext()
    {
        // returns true if the enumeration isn't over
        // returns false if there are no more elements
    }
}
```

You may iterate over an iterable collection using the following code:

```
it = collection.iterator();
while(it.hasNext()) {
    x = it.next();
   
    // do something with x
    // x is an element of the collection
    Console.print(x);
}
```

Or, alternatively, using the compact foreach:

```
foreach(x in collection) {
    Console.print(x);
}
```

For the sake of completion, the following code demonstrates how to implement a custom iterable collection that hold even numbers from 0 up to 20 without having to store them explicitly in memory:

```
object "Application"
{
    evenNumbers = spawn("Even Numbers").upTo(20);

    state "main"
    {
        // print all the numbers of the iterable collection
        foreach(number in evenNumbers)
            Console.print(number);

        // we're done!
        exit();
    }
}

object "Even Numbers" is "iterable"
{
    lastNumber = 0;

    fun iterator()
    {
        return spawn("Even Numbers Iterator").upTo(lastNumber);
    }

    fun upTo(num)
    {
        // upTo() is a chainable function that
        // is NOT part of the iterator protocol
        // (but it's useful for this example)
        lastNumber = Number(num);
        return this;
    }
}

object "Even Numbers Iterator" is "iterator"
{
    nextNumber = 0;
    lastNumber = 0;

    fun next()
    {
        currentNumber = nextNumber;
        nextNumber += 2;
        return currentNumber;
    }

    fun hasNext()
    {
        return nextNumber <= lastNumber;
    }

    fun upTo(num)
    {
        // upTo() is a chainable function that
        // is NOT part of the iterator protocol
        // (but it's useful for this example)
        lastNumber = Number(num);
        return this;
    }
}
```

The output of this code is:

```
0
2
4
6
8
10
12
14
16
18
20
```

**CHALLENGE:** can you write an iterable collection called *Fibonacci Sequence* containing the first *N* [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) without storing them all explicitly in memory? It should be used as follows:
```
// Desired output (for N=10): 0 1 1 2 3 5 8 13 21 34
sequence = spawn("Fibonacci Sequence").ofLength(10);
foreach(number in sequence)
    Console.print(number);
```