firefly-compiler 0.5.39 → 0.5.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.hintrc +4 -4
- package/.vscode/settings.json +4 -4
- package/bin/Release.ff +158 -157
- package/bin/firefly.mjs +1 -1
- package/compiler/Builder.ff +275 -275
- package/compiler/Compiler.ff +234 -234
- package/compiler/Dependencies.ff +186 -186
- package/compiler/DependencyLock.ff +17 -17
- package/compiler/JsEmitter.ff +1437 -1437
- package/compiler/LspHook.ff +202 -202
- package/compiler/ModuleCache.ff +178 -178
- package/compiler/Workspace.ff +88 -88
- package/core/.firefly/include/package.json +5 -5
- package/core/.firefly/package.ff +2 -2
- package/core/Any.ff +25 -25
- package/core/Array.ff +298 -298
- package/core/Atomic.ff +63 -63
- package/core/Box.ff +7 -7
- package/core/BrowserSystem.ff +40 -40
- package/core/BuildSystem.ff +156 -156
- package/core/Crypto.ff +94 -94
- package/core/Equal.ff +41 -41
- package/core/Error.ff +25 -25
- package/core/HttpClient.ff +142 -142
- package/core/Instant.ff +24 -24
- package/core/Js.ff +305 -305
- package/core/JsSystem.ff +135 -135
- package/core/Json.ff +423 -423
- package/core/List.ff +482 -482
- package/core/Lock.ff +108 -108
- package/core/NodeSystem.ff +198 -198
- package/core/Ordering.ff +160 -160
- package/core/Path.ff +377 -378
- package/core/Queue.ff +90 -90
- package/core/Random.ff +140 -140
- package/core/RbMap.ff +216 -216
- package/core/Show.ff +44 -44
- package/core/SourceLocation.ff +68 -68
- package/core/Task.ff +165 -165
- package/experimental/benchmarks/ListGrab.ff +23 -23
- package/experimental/benchmarks/ListGrab.java +55 -55
- package/experimental/benchmarks/Pyrotek45.ff +30 -30
- package/experimental/benchmarks/Pyrotek45.java +64 -64
- package/experimental/bidirectional/Bidi.ff +88 -88
- package/experimental/lines/Main.ff +40 -40
- package/experimental/random/Index.ff +53 -53
- package/experimental/random/Process.ff +120 -120
- package/experimental/random/RunLength.ff +65 -65
- package/experimental/random/Scrape.ff +51 -51
- package/experimental/random/Symbols.ff +73 -73
- package/experimental/random/Tensor.ff +52 -52
- package/experimental/random/Units.ff +36 -36
- package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
- package/experimental/s3/S3TestPut.ff +16 -16
- package/experimental/tests/TestJson.ff +26 -26
- package/firefly.sh +0 -0
- package/fireflysite/.firefly/package.ff +4 -4
- package/fireflysite/CommunityOverview.ff +20 -20
- package/fireflysite/CountingButtonDemo.ff +58 -58
- package/fireflysite/DocumentParser.ff +325 -325
- package/fireflysite/ExamplesOverview.ff +40 -40
- package/fireflysite/FrontPage.ff +344 -344
- package/fireflysite/GettingStarted.ff +45 -45
- package/fireflysite/Guide.ff +456 -456
- package/fireflysite/Main.ff +163 -163
- package/fireflysite/MatchingPasswordsDemo.ff +82 -82
- package/fireflysite/PackagesOverview.ff +49 -49
- package/fireflysite/PostgresqlDemo.ff +34 -34
- package/fireflysite/ReferenceAll.ff +18 -18
- package/fireflysite/ReferenceIntroduction.ff +11 -11
- package/fireflysite/Styles.ff +567 -567
- package/fireflysite/Test.ff +121 -121
- package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -209
- package/fireflysite/assets/markdown/reference/EmittedJavascript.md +65 -65
- package/fireflysite/assets/markdown/reference/Exceptions.md +101 -101
- package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +364 -364
- package/fireflysite/assets/markdown/reference/JavascriptInterop.md +235 -235
- package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -162
- package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -48
- package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -224
- package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -86
- package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -99
- package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -100
- package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -184
- package/fireflysite/assets/markdown/scratch/ControlFlow.md +136 -136
- package/fireflysite/assets/markdown/scratch/Toc.md +40 -40
- package/lsp/.firefly/package.ff +1 -1
- package/lsp/CompletionHandler.ff +827 -827
- package/lsp/Handler.ff +714 -714
- package/lsp/HoverHandler.ff +79 -79
- package/lsp/LanguageServer.ff +272 -272
- package/lsp/SignatureHelpHandler.ff +55 -55
- package/lsp/SymbolHandler.ff +181 -181
- package/lsp/TestReferences.ff +17 -17
- package/lsp/TestReferencesCase.ff +7 -7
- package/lsp/stderr.txt +1 -1
- package/lsp/stdout.txt +34 -34
- package/lux/.firefly/package.ff +1 -1
- package/lux/Css.ff +648 -648
- package/lux/CssTest.ff +48 -48
- package/lux/Lux.ff +608 -608
- package/lux/LuxEvent.ff +79 -79
- package/lux/Main.ff +123 -123
- package/lux/Main2.ff +143 -143
- package/lux/TestDry.ff +28 -28
- package/output/js/ff/compiler/Builder.mjs +36 -36
- package/output/js/ff/core/Path.mjs +0 -2
- package/package.json +1 -1
- package/rpc/.firefly/package.ff +1 -1
- package/rpc/Rpc.ff +70 -70
- package/s3/.firefly/package.ff +1 -1
- package/s3/S3.ff +92 -92
- package/vscode/LICENSE.txt +21 -21
- package/vscode/Prepublish.ff +15 -15
- package/vscode/README.md +16 -16
- package/vscode/client/package-lock.json +544 -544
- package/vscode/client/package.json +22 -22
- package/vscode/client/src/extension.ts +104 -104
- package/vscode/icons/firefly-icon.svg +10 -10
- package/vscode/language-configuration.json +61 -61
- package/vscode/package-lock.json +3623 -3623
- package/vscode/package.json +1 -1
- package/vscode/snippets.json +241 -241
- package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
- package/webserver/.firefly/include/package.json +5 -5
- package/webserver/.firefly/package.ff +2 -2
- package/webserver/WebServer.ff +647 -647
- package/websocket/.firefly/package.ff +1 -1
- package/websocket/WebSocket.ff +100 -100
|
@@ -1,365 +1,365 @@
|
|
|
1
|
-
# Functions and methods
|
|
2
|
-
|
|
3
|
-
There are 5 kinds of functions in Firefly:
|
|
4
|
-
|
|
5
|
-
* Top-level functions
|
|
6
|
-
* Anonymous functions
|
|
7
|
-
* Local functions
|
|
8
|
-
* Methods
|
|
9
|
-
* Trait functions
|
|
10
|
-
|
|
11
|
-
Trait functions are covered in section [Traits and instances](traits-and-instances), the rest are covered below.
|
|
12
|
-
|
|
13
|
-
# Top-level functions
|
|
14
|
-
|
|
15
|
-
Functions at the top level are defined like this:
|
|
16
|
-
|
|
17
|
-
```firefly
|
|
18
|
-
add(a: Int, b: Int): Int {
|
|
19
|
-
let sum = a + b
|
|
20
|
-
sum
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
This function takes two arguments of type `Int` and returns their sum as an `Int`. A function with a return type other than `Unit`, must have an expression as the last statement, which is returned. In the example above, the value of `sum` is returned.
|
|
25
|
-
|
|
26
|
-
When a function returns Unit, it can end with any statement.
|
|
27
|
-
|
|
28
|
-
```firefly
|
|
29
|
-
class Counter(mutable value: Int)
|
|
30
|
-
|
|
31
|
-
reset(counter: Counter): Unit {
|
|
32
|
-
counter.value = 0
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The example above defines a type `Counter` with a mutable field. The function `reset` sets this value to zero.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
A `Unit` return type can be omitted in a function definition, like this:
|
|
40
|
-
|
|
41
|
-
```firefly
|
|
42
|
-
reset(counter: Counter) {
|
|
43
|
-
counter.value = 0
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
The parameter types must be declares expitly and the parentheses are required, even if there are no parameters. The function name and parameter names must start with a lowercase letter.
|
|
48
|
-
|
|
49
|
-
# Type parameters
|
|
50
|
-
|
|
51
|
-
In a function signature, you can introduce a list of type parameters enclosed in square brackets, immediately following the function name. These type parameters can be used in the rest of the signature.
|
|
52
|
-
|
|
53
|
-
The following example defines a function `swap` that takes any Pair and returns a Pair with first and second values swapped:
|
|
54
|
-
|
|
55
|
-
```firefly
|
|
56
|
-
swap[A, B](pair: Pair[A, B]): Pair[B, A] {
|
|
57
|
-
Pair(pair.second, pair.first)
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Two type parameters `A` and `B` are first introduced in the square brackets. These type parameters are swapped in the input and return type, expressing the value swap at type level. The type parameters are unbounded in the sense that `swap` may be called with `A` and `B` replaced by any types.
|
|
62
|
-
|
|
63
|
-
Type parameters can be bounded or constrained like this:
|
|
64
|
-
|
|
65
|
-
```firefly
|
|
66
|
-
same[E: Equal](pair: Pair[E, E]): Bool {
|
|
67
|
-
pair.first == pair.second
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
The type parameter `E` must implement the Equal trait, which is required to perform equality comparisons. The section on [traits and instances](traits-and-instances) will discuss constraints in more detail.
|
|
72
|
-
|
|
73
|
-
Firefly cannot operate on the concrete types of type parameters at runtime. The behavior of a function is limited to what is specified in the function signature.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# Calling functions
|
|
77
|
-
|
|
78
|
-
Functions are called like this:
|
|
79
|
-
|
|
80
|
-
```firefly
|
|
81
|
-
add(1, 2) // 3
|
|
82
|
-
same(Pair('A', 'a')) // False
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Just like variants, the arguments can be named, and given out of order, like this:
|
|
86
|
-
|
|
87
|
-
```firefly
|
|
88
|
-
add(b = 2, a = 1)
|
|
89
|
-
add(b = 2, 1) // Same as above
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
# Default Values
|
|
93
|
-
|
|
94
|
-
Function parameters in Firefly can have default values, which are used when no argument is provided for that parameter during a function call. Default values are specified in the function signature by assigning a value to the parameter.
|
|
95
|
-
|
|
96
|
-
Here’s an example of a function with a default value:
|
|
97
|
-
|
|
98
|
-
```firefly
|
|
99
|
-
add(a: Int, b: Int = 1): Int {
|
|
100
|
-
a + b
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
When called without the second arguments, the function will use the default value:
|
|
105
|
-
|
|
106
|
-
```firefly
|
|
107
|
-
add(1) // returns 2
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
If an argument is provided, it overrides the default:
|
|
111
|
-
|
|
112
|
-
```firefly
|
|
113
|
-
add(1, 2) // returns 3
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# Recursion
|
|
118
|
-
|
|
119
|
-
Function definitions can be recursive, meaning that a function can call itself. Here’s an example of a recursive function that calculates the factorial of a number:
|
|
120
|
-
|
|
121
|
-
```firefly
|
|
122
|
-
factorial(n: Int): Int {
|
|
123
|
-
if(n == 0) {
|
|
124
|
-
1
|
|
125
|
-
} else {
|
|
126
|
-
n * factorial(n - 1)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
When calling `factorial(0)`, the base case is triggered, returning `1`. Calling the function with a positive integer will recursively calculate the factorial. However, calling it with negative values will result in infinite recursion, as no base case exists for such input.
|
|
132
|
-
|
|
133
|
-
# Tail Recursion
|
|
134
|
-
|
|
135
|
-
In some recursive functions, the recursive call is the last operation performed before returning the result. This is _tail recursion_. The Firefly compiler can optimize tail recursive calls, avoiding the buildup of function calls on the stack.
|
|
136
|
-
|
|
137
|
-
You can use the `tailcall` keyword to explicitly mark a recursive call as tail-recursive, ensuring the compiler applies the optimization.
|
|
138
|
-
|
|
139
|
-
Here is a tail-recursive implementation of the factorial function:
|
|
140
|
-
|
|
141
|
-
```firefly
|
|
142
|
-
factorial(n: Int, acc: Int = 1): Int {
|
|
143
|
-
if(n == 0) {
|
|
144
|
-
acc
|
|
145
|
-
} else {
|
|
146
|
-
tailcall factorial(n - 1, n * acc)
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
This version introduces an additional parameter, `acc`, which acts as an accumulator to hold the running result of the factorial calculation. It is initialized to 1 by default, ensuring that when the function is first called with a single argument, the computation starts correctly. By moving the multiplication into the recursive call via the accumulator, the call becomes a tail-call, allowing the Firefly compiler to optimize it.
|
|
152
|
-
|
|
153
|
-
# Anonymous functions
|
|
154
|
-
|
|
155
|
-
In firefly anonymous functions are written in curlybrases and constucted like this:
|
|
156
|
-
|
|
157
|
-
```firefly
|
|
158
|
-
{a, b =>
|
|
159
|
-
let sum = a + b
|
|
160
|
-
sum
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
This anonymous function takes two arguments and returns their sum. Like named functions, the body is a sequence of statements where the last expression is returned.
|
|
165
|
-
|
|
166
|
-
Anonymous functions are often used right away, like below:
|
|
167
|
-
|
|
168
|
-
```firefly
|
|
169
|
-
[1, 2, 3].map({x => x + 1}) // Returns [2, 3, 4]
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
An anonymous function that increments the given value by one is passed as argument to the method `map` working on lists.
|
|
173
|
-
|
|
174
|
-
These functions are anonymous in the sense that they do not bring a name into scope themselves. They are just expressions that construct a function value. Like all other values, they can be assigned to variables, passed as arguments, or returned from other functions. But unlike other values, they can also be called.
|
|
175
|
-
|
|
176
|
-
This is in contrast to named functions, which are not first-class in Firefly. The name of a top-level function can only be called but is not an expression in Firefly. To pass a top-level function as an argument, for instance, it must be converted to an anonymous function first.
|
|
177
|
-
|
|
178
|
-
The type of function values are writen like this:
|
|
179
|
-
|
|
180
|
-
```firefly
|
|
181
|
-
Int => Int // One parameter
|
|
182
|
-
(Int, Int) => Int // Multiple parameters
|
|
183
|
-
() => Int // No parameters
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
The type of an anonymous function cannot be written explicitly in the definition but is inferred from its usage. It will always have a monomorphic type where the argument and return types are concrete types.
|
|
187
|
-
|
|
188
|
-
Here are some examples of anonymous functions assigned to variables explicitly given a type.
|
|
189
|
-
|
|
190
|
-
Anonymous function without parameters are written without the arrow (`=>`), like this:
|
|
191
|
-
|
|
192
|
-
```firefly
|
|
193
|
-
let life: () => Int = {42}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
This is an anonymous function taking no arguments and returning `Unit`:
|
|
197
|
-
|
|
198
|
-
```firefly
|
|
199
|
-
let unit: () => Unit = {}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
This is an anonymous function that increments its input by one:
|
|
203
|
-
|
|
204
|
-
```firefly
|
|
205
|
-
let next: Int => Int = {i => i + 1}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
This anonymous function takes multiple arguments:
|
|
209
|
-
|
|
210
|
-
```firefly
|
|
211
|
-
let plus: (Int, Int) => Int = {a, b => a + b}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
Anonymous function are called like named function.
|
|
215
|
-
|
|
216
|
-
```firefly
|
|
217
|
-
life() // returns 42
|
|
218
|
-
unit() // returns unit
|
|
219
|
-
next(1) // returns 2
|
|
220
|
-
plus(1, 2) // returns 3
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
Parameter names are not part of the function type, and likewise, anonymous functions cannot be called with named arguments. The same goes for default argument values, which are not supported for anonymous functions.
|
|
224
|
-
|
|
225
|
-
The parameter list and the function arrow can be omitted when the parameters are only used once in the function body. In such cases, the parameters in the body are replaced with underscores (`_`), like this:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
```firefly
|
|
229
|
-
let next: Int => Int = {_ + 1}
|
|
230
|
-
let plus: (Int, Int) => Int = {_ + _}
|
|
231
|
-
let identity: Int => Int = {_}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
These underscores, or anonymous parameters, always belong to the nearest anonymous function. Consider the following function:
|
|
235
|
-
|
|
236
|
-
```firefly
|
|
237
|
-
let f: Int => Int = {{_ + 1}(_)}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
In this code, there is an outer and an inner anonymous function, both taking one argument. The first underscore belongs to the inner function, which is called immediately by the outer function with the outer function's anonymous parameter as the argument.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
# Trailing Anonymous Function Arguments
|
|
244
|
-
|
|
245
|
-
Firefly has a special syntax for calling functions with function arguments. When a call has a sequence of literal anonymous functions as the last arguments, these arguments may be given using this special syntax. Consider the `if` function from the standard library, with this signature:
|
|
246
|
-
|
|
247
|
-
```firefly
|
|
248
|
-
if[T](condition: Bool, body: () => T): Option[T]
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
The `if` function takes two parameters, where the last is a function. Calling `if` with the standard syntax could look like this:
|
|
252
|
-
|
|
253
|
-
```firefly
|
|
254
|
-
if(x == 0, {"Zero"})
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
Using the special syntax for trailing anonymous function arguments, it looks like this:
|
|
258
|
-
|
|
259
|
-
```firefly
|
|
260
|
-
if(x == 0) {"Zero"}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
With this syntax, the anonymous function is written after the call parentheses. Multiple trailing function arguments may be given in sequence. Consider the `while` function from the standard library, with this signature:
|
|
264
|
-
|
|
265
|
-
```firefly
|
|
266
|
-
while(condition: () => Bool, body: () => Unit): Unit
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
The `while` function takes two parameters, both functions. Using the special syntax, a call to `while` may look like this:
|
|
270
|
-
|
|
271
|
-
```firefly
|
|
272
|
-
while {array.size() < 5} {
|
|
273
|
-
array.push("X")
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
The code above will push a string to an array while the size of the array is less than 5.
|
|
278
|
-
|
|
279
|
-
This syntax for trailing anonymous function arguments allows the use of `if` and `while` to resemble constructs in languages such as C and JavaScript, where these constructs are built-in keywords rather than functions.
|
|
280
|
-
|
|
281
|
-
# Trailing Colon Function Argument
|
|
282
|
-
|
|
283
|
-
Firefly has a variation of the trailing anonymous function argument syntax. The very last anonymous function argument may be written after a colon, without curly braces. The purpose of this syntax is to avoid aditional indentation.
|
|
284
|
-
|
|
285
|
-
The example below calls `if` with a trailing colon function argument:
|
|
286
|
-
|
|
287
|
-
```firefly
|
|
288
|
-
safeFactorial(n: Int, acc: Int = 1): Option[Int] {
|
|
289
|
-
if(n >= 0):
|
|
290
|
-
factorial(n)
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
In this example, the `if` function is called with the condition `n >= 0` and an anonymous function that computes `factorial(n)`. If `n` is negative, the `if` function returns `None` and so does `safeFactorial`. Using the colon syntax, the `safeFactorial` functions continues unindented otherwise.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
# Local functions
|
|
298
|
-
|
|
299
|
-
Local functions are declared exactly like top-level functions but with the `function` keyword in front of the signature, like this:
|
|
300
|
-
|
|
301
|
-
```firefly
|
|
302
|
-
function square(n: Int): Int {
|
|
303
|
-
n * n
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
The above local function definition is a statement, similar to local variables declared with `let`. The function name `square` will be in scope for the rest of the code block.
|
|
308
|
-
|
|
309
|
-
Furthermore, local functions declared in sequence are in scope within each other's bodies, allowing them to be mutually recursive.
|
|
310
|
-
|
|
311
|
-
# Methods
|
|
312
|
-
|
|
313
|
-
Firefly has methods, which are called like this:
|
|
314
|
-
|
|
315
|
-
```firefly
|
|
316
|
-
Some(1).isEmpty() // False
|
|
317
|
-
Some(1).map({_ + 1}) // Some(2)
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
The examples above, calls the two methods `isEmpty` and `map` defined on `Option`. The code below, shows how these methods are defined in `ff:core` package:
|
|
321
|
-
|
|
322
|
-
```firefly
|
|
323
|
-
extend self[T]: Option[T] {
|
|
324
|
-
isEmpty(): Bool {...}
|
|
325
|
-
map[R](body: T => R): Option[R] {...}
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
The `extend` keyword is used to declare methods on values of a given type. The code above extend values of type `Option[T]` with two methods. The identifier `self` holds a reference to the value receiving the methods. The square bracket right after the self variable are optional and used to introduce type variables used to express the type extended with methods.
|
|
330
|
-
|
|
331
|
-
The two methods above are defined for all values of type `Option[T]`, but methods can be også be defined for a more narrow targer type, like `flatten` below:
|
|
332
|
-
|
|
333
|
-
```firefly
|
|
334
|
-
extend self[T]: Option[Option[T]] {
|
|
335
|
-
flatten(): Option[T] {...}
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
The extend block above declares `self` as ` Option[Option[T]]`, and likewise will only define `flatten` for options types of options.
|
|
340
|
-
|
|
341
|
-
The scope of a method can also be narrowed down with trait constraints.
|
|
342
|
-
|
|
343
|
-
```firefly
|
|
344
|
-
extend self[T: Equal]: Option[T] {
|
|
345
|
-
contains(value: T): Bool {...}
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
In code above, the extend block defines methods for the target type `Option[T]`, but only when `T` implements the `Equal` trait.
|
|
350
|
-
|
|
351
|
-
Extend blocks must reside in the same module at definition of the type that are extended with methods.
|
|
352
|
-
|
|
353
|
-
Methods are equivalent to top level functions in terms of expressibility but differnt in terms of scoping. Each type definition has its own method scope.
|
|
354
|
-
|
|
355
|
-
# Special method call syntax
|
|
356
|
-
|
|
357
|
-
```firefly
|
|
358
|
-
if(x == 1) {"One"} else {"Several"}
|
|
359
|
-
Some(1).map {_ + 1} // Some(2)
|
|
360
|
-
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
# Trait functions
|
|
364
|
-
|
|
1
|
+
# Functions and methods
|
|
2
|
+
|
|
3
|
+
There are 5 kinds of functions in Firefly:
|
|
4
|
+
|
|
5
|
+
* Top-level functions
|
|
6
|
+
* Anonymous functions
|
|
7
|
+
* Local functions
|
|
8
|
+
* Methods
|
|
9
|
+
* Trait functions
|
|
10
|
+
|
|
11
|
+
Trait functions are covered in section [Traits and instances](traits-and-instances), the rest are covered below.
|
|
12
|
+
|
|
13
|
+
# Top-level functions
|
|
14
|
+
|
|
15
|
+
Functions at the top level are defined like this:
|
|
16
|
+
|
|
17
|
+
```firefly
|
|
18
|
+
add(a: Int, b: Int): Int {
|
|
19
|
+
let sum = a + b
|
|
20
|
+
sum
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This function takes two arguments of type `Int` and returns their sum as an `Int`. A function with a return type other than `Unit`, must have an expression as the last statement, which is returned. In the example above, the value of `sum` is returned.
|
|
25
|
+
|
|
26
|
+
When a function returns Unit, it can end with any statement.
|
|
27
|
+
|
|
28
|
+
```firefly
|
|
29
|
+
class Counter(mutable value: Int)
|
|
30
|
+
|
|
31
|
+
reset(counter: Counter): Unit {
|
|
32
|
+
counter.value = 0
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The example above defines a type `Counter` with a mutable field. The function `reset` sets this value to zero.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
A `Unit` return type can be omitted in a function definition, like this:
|
|
40
|
+
|
|
41
|
+
```firefly
|
|
42
|
+
reset(counter: Counter) {
|
|
43
|
+
counter.value = 0
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The parameter types must be declares expitly and the parentheses are required, even if there are no parameters. The function name and parameter names must start with a lowercase letter.
|
|
48
|
+
|
|
49
|
+
# Type parameters
|
|
50
|
+
|
|
51
|
+
In a function signature, you can introduce a list of type parameters enclosed in square brackets, immediately following the function name. These type parameters can be used in the rest of the signature.
|
|
52
|
+
|
|
53
|
+
The following example defines a function `swap` that takes any Pair and returns a Pair with first and second values swapped:
|
|
54
|
+
|
|
55
|
+
```firefly
|
|
56
|
+
swap[A, B](pair: Pair[A, B]): Pair[B, A] {
|
|
57
|
+
Pair(pair.second, pair.first)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Two type parameters `A` and `B` are first introduced in the square brackets. These type parameters are swapped in the input and return type, expressing the value swap at type level. The type parameters are unbounded in the sense that `swap` may be called with `A` and `B` replaced by any types.
|
|
62
|
+
|
|
63
|
+
Type parameters can be bounded or constrained like this:
|
|
64
|
+
|
|
65
|
+
```firefly
|
|
66
|
+
same[E: Equal](pair: Pair[E, E]): Bool {
|
|
67
|
+
pair.first == pair.second
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The type parameter `E` must implement the Equal trait, which is required to perform equality comparisons. The section on [traits and instances](traits-and-instances) will discuss constraints in more detail.
|
|
72
|
+
|
|
73
|
+
Firefly cannot operate on the concrete types of type parameters at runtime. The behavior of a function is limited to what is specified in the function signature.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Calling functions
|
|
77
|
+
|
|
78
|
+
Functions are called like this:
|
|
79
|
+
|
|
80
|
+
```firefly
|
|
81
|
+
add(1, 2) // 3
|
|
82
|
+
same(Pair('A', 'a')) // False
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Just like variants, the arguments can be named, and given out of order, like this:
|
|
86
|
+
|
|
87
|
+
```firefly
|
|
88
|
+
add(b = 2, a = 1)
|
|
89
|
+
add(b = 2, 1) // Same as above
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
# Default Values
|
|
93
|
+
|
|
94
|
+
Function parameters in Firefly can have default values, which are used when no argument is provided for that parameter during a function call. Default values are specified in the function signature by assigning a value to the parameter.
|
|
95
|
+
|
|
96
|
+
Here’s an example of a function with a default value:
|
|
97
|
+
|
|
98
|
+
```firefly
|
|
99
|
+
add(a: Int, b: Int = 1): Int {
|
|
100
|
+
a + b
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
When called without the second arguments, the function will use the default value:
|
|
105
|
+
|
|
106
|
+
```firefly
|
|
107
|
+
add(1) // returns 2
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If an argument is provided, it overrides the default:
|
|
111
|
+
|
|
112
|
+
```firefly
|
|
113
|
+
add(1, 2) // returns 3
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Recursion
|
|
118
|
+
|
|
119
|
+
Function definitions can be recursive, meaning that a function can call itself. Here’s an example of a recursive function that calculates the factorial of a number:
|
|
120
|
+
|
|
121
|
+
```firefly
|
|
122
|
+
factorial(n: Int): Int {
|
|
123
|
+
if(n == 0) {
|
|
124
|
+
1
|
|
125
|
+
} else {
|
|
126
|
+
n * factorial(n - 1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
When calling `factorial(0)`, the base case is triggered, returning `1`. Calling the function with a positive integer will recursively calculate the factorial. However, calling it with negative values will result in infinite recursion, as no base case exists for such input.
|
|
132
|
+
|
|
133
|
+
# Tail Recursion
|
|
134
|
+
|
|
135
|
+
In some recursive functions, the recursive call is the last operation performed before returning the result. This is _tail recursion_. The Firefly compiler can optimize tail recursive calls, avoiding the buildup of function calls on the stack.
|
|
136
|
+
|
|
137
|
+
You can use the `tailcall` keyword to explicitly mark a recursive call as tail-recursive, ensuring the compiler applies the optimization.
|
|
138
|
+
|
|
139
|
+
Here is a tail-recursive implementation of the factorial function:
|
|
140
|
+
|
|
141
|
+
```firefly
|
|
142
|
+
factorial(n: Int, acc: Int = 1): Int {
|
|
143
|
+
if(n == 0) {
|
|
144
|
+
acc
|
|
145
|
+
} else {
|
|
146
|
+
tailcall factorial(n - 1, n * acc)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This version introduces an additional parameter, `acc`, which acts as an accumulator to hold the running result of the factorial calculation. It is initialized to 1 by default, ensuring that when the function is first called with a single argument, the computation starts correctly. By moving the multiplication into the recursive call via the accumulator, the call becomes a tail-call, allowing the Firefly compiler to optimize it.
|
|
152
|
+
|
|
153
|
+
# Anonymous functions
|
|
154
|
+
|
|
155
|
+
In firefly anonymous functions are written in curlybrases and constucted like this:
|
|
156
|
+
|
|
157
|
+
```firefly
|
|
158
|
+
{a, b =>
|
|
159
|
+
let sum = a + b
|
|
160
|
+
sum
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This anonymous function takes two arguments and returns their sum. Like named functions, the body is a sequence of statements where the last expression is returned.
|
|
165
|
+
|
|
166
|
+
Anonymous functions are often used right away, like below:
|
|
167
|
+
|
|
168
|
+
```firefly
|
|
169
|
+
[1, 2, 3].map({x => x + 1}) // Returns [2, 3, 4]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
An anonymous function that increments the given value by one is passed as argument to the method `map` working on lists.
|
|
173
|
+
|
|
174
|
+
These functions are anonymous in the sense that they do not bring a name into scope themselves. They are just expressions that construct a function value. Like all other values, they can be assigned to variables, passed as arguments, or returned from other functions. But unlike other values, they can also be called.
|
|
175
|
+
|
|
176
|
+
This is in contrast to named functions, which are not first-class in Firefly. The name of a top-level function can only be called but is not an expression in Firefly. To pass a top-level function as an argument, for instance, it must be converted to an anonymous function first.
|
|
177
|
+
|
|
178
|
+
The type of function values are writen like this:
|
|
179
|
+
|
|
180
|
+
```firefly
|
|
181
|
+
Int => Int // One parameter
|
|
182
|
+
(Int, Int) => Int // Multiple parameters
|
|
183
|
+
() => Int // No parameters
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The type of an anonymous function cannot be written explicitly in the definition but is inferred from its usage. It will always have a monomorphic type where the argument and return types are concrete types.
|
|
187
|
+
|
|
188
|
+
Here are some examples of anonymous functions assigned to variables explicitly given a type.
|
|
189
|
+
|
|
190
|
+
Anonymous function without parameters are written without the arrow (`=>`), like this:
|
|
191
|
+
|
|
192
|
+
```firefly
|
|
193
|
+
let life: () => Int = {42}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
This is an anonymous function taking no arguments and returning `Unit`:
|
|
197
|
+
|
|
198
|
+
```firefly
|
|
199
|
+
let unit: () => Unit = {}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This is an anonymous function that increments its input by one:
|
|
203
|
+
|
|
204
|
+
```firefly
|
|
205
|
+
let next: Int => Int = {i => i + 1}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
This anonymous function takes multiple arguments:
|
|
209
|
+
|
|
210
|
+
```firefly
|
|
211
|
+
let plus: (Int, Int) => Int = {a, b => a + b}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Anonymous function are called like named function.
|
|
215
|
+
|
|
216
|
+
```firefly
|
|
217
|
+
life() // returns 42
|
|
218
|
+
unit() // returns unit
|
|
219
|
+
next(1) // returns 2
|
|
220
|
+
plus(1, 2) // returns 3
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Parameter names are not part of the function type, and likewise, anonymous functions cannot be called with named arguments. The same goes for default argument values, which are not supported for anonymous functions.
|
|
224
|
+
|
|
225
|
+
The parameter list and the function arrow can be omitted when the parameters are only used once in the function body. In such cases, the parameters in the body are replaced with underscores (`_`), like this:
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
```firefly
|
|
229
|
+
let next: Int => Int = {_ + 1}
|
|
230
|
+
let plus: (Int, Int) => Int = {_ + _}
|
|
231
|
+
let identity: Int => Int = {_}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
These underscores, or anonymous parameters, always belong to the nearest anonymous function. Consider the following function:
|
|
235
|
+
|
|
236
|
+
```firefly
|
|
237
|
+
let f: Int => Int = {{_ + 1}(_)}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
In this code, there is an outer and an inner anonymous function, both taking one argument. The first underscore belongs to the inner function, which is called immediately by the outer function with the outer function's anonymous parameter as the argument.
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# Trailing Anonymous Function Arguments
|
|
244
|
+
|
|
245
|
+
Firefly has a special syntax for calling functions with function arguments. When a call has a sequence of literal anonymous functions as the last arguments, these arguments may be given using this special syntax. Consider the `if` function from the standard library, with this signature:
|
|
246
|
+
|
|
247
|
+
```firefly
|
|
248
|
+
if[T](condition: Bool, body: () => T): Option[T]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The `if` function takes two parameters, where the last is a function. Calling `if` with the standard syntax could look like this:
|
|
252
|
+
|
|
253
|
+
```firefly
|
|
254
|
+
if(x == 0, {"Zero"})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Using the special syntax for trailing anonymous function arguments, it looks like this:
|
|
258
|
+
|
|
259
|
+
```firefly
|
|
260
|
+
if(x == 0) {"Zero"}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
With this syntax, the anonymous function is written after the call parentheses. Multiple trailing function arguments may be given in sequence. Consider the `while` function from the standard library, with this signature:
|
|
264
|
+
|
|
265
|
+
```firefly
|
|
266
|
+
while(condition: () => Bool, body: () => Unit): Unit
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The `while` function takes two parameters, both functions. Using the special syntax, a call to `while` may look like this:
|
|
270
|
+
|
|
271
|
+
```firefly
|
|
272
|
+
while {array.size() < 5} {
|
|
273
|
+
array.push("X")
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The code above will push a string to an array while the size of the array is less than 5.
|
|
278
|
+
|
|
279
|
+
This syntax for trailing anonymous function arguments allows the use of `if` and `while` to resemble constructs in languages such as C and JavaScript, where these constructs are built-in keywords rather than functions.
|
|
280
|
+
|
|
281
|
+
# Trailing Colon Function Argument
|
|
282
|
+
|
|
283
|
+
Firefly has a variation of the trailing anonymous function argument syntax. The very last anonymous function argument may be written after a colon, without curly braces. The purpose of this syntax is to avoid aditional indentation.
|
|
284
|
+
|
|
285
|
+
The example below calls `if` with a trailing colon function argument:
|
|
286
|
+
|
|
287
|
+
```firefly
|
|
288
|
+
safeFactorial(n: Int, acc: Int = 1): Option[Int] {
|
|
289
|
+
if(n >= 0):
|
|
290
|
+
factorial(n)
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
In this example, the `if` function is called with the condition `n >= 0` and an anonymous function that computes `factorial(n)`. If `n` is negative, the `if` function returns `None` and so does `safeFactorial`. Using the colon syntax, the `safeFactorial` functions continues unindented otherwise.
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# Local functions
|
|
298
|
+
|
|
299
|
+
Local functions are declared exactly like top-level functions but with the `function` keyword in front of the signature, like this:
|
|
300
|
+
|
|
301
|
+
```firefly
|
|
302
|
+
function square(n: Int): Int {
|
|
303
|
+
n * n
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The above local function definition is a statement, similar to local variables declared with `let`. The function name `square` will be in scope for the rest of the code block.
|
|
308
|
+
|
|
309
|
+
Furthermore, local functions declared in sequence are in scope within each other's bodies, allowing them to be mutually recursive.
|
|
310
|
+
|
|
311
|
+
# Methods
|
|
312
|
+
|
|
313
|
+
Firefly has methods, which are called like this:
|
|
314
|
+
|
|
315
|
+
```firefly
|
|
316
|
+
Some(1).isEmpty() // False
|
|
317
|
+
Some(1).map({_ + 1}) // Some(2)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The examples above, calls the two methods `isEmpty` and `map` defined on `Option`. The code below, shows how these methods are defined in `ff:core` package:
|
|
321
|
+
|
|
322
|
+
```firefly
|
|
323
|
+
extend self[T]: Option[T] {
|
|
324
|
+
isEmpty(): Bool {...}
|
|
325
|
+
map[R](body: T => R): Option[R] {...}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The `extend` keyword is used to declare methods on values of a given type. The code above extend values of type `Option[T]` with two methods. The identifier `self` holds a reference to the value receiving the methods. The square bracket right after the self variable are optional and used to introduce type variables used to express the type extended with methods.
|
|
330
|
+
|
|
331
|
+
The two methods above are defined for all values of type `Option[T]`, but methods can be også be defined for a more narrow targer type, like `flatten` below:
|
|
332
|
+
|
|
333
|
+
```firefly
|
|
334
|
+
extend self[T]: Option[Option[T]] {
|
|
335
|
+
flatten(): Option[T] {...}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The extend block above declares `self` as ` Option[Option[T]]`, and likewise will only define `flatten` for options types of options.
|
|
340
|
+
|
|
341
|
+
The scope of a method can also be narrowed down with trait constraints.
|
|
342
|
+
|
|
343
|
+
```firefly
|
|
344
|
+
extend self[T: Equal]: Option[T] {
|
|
345
|
+
contains(value: T): Bool {...}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
In code above, the extend block defines methods for the target type `Option[T]`, but only when `T` implements the `Equal` trait.
|
|
350
|
+
|
|
351
|
+
Extend blocks must reside in the same module at definition of the type that are extended with methods.
|
|
352
|
+
|
|
353
|
+
Methods are equivalent to top level functions in terms of expressibility but differnt in terms of scoping. Each type definition has its own method scope.
|
|
354
|
+
|
|
355
|
+
# Special method call syntax
|
|
356
|
+
|
|
357
|
+
```firefly
|
|
358
|
+
if(x == 1) {"One"} else {"Several"}
|
|
359
|
+
Some(1).map {_ + 1} // Some(2)
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
# Trait functions
|
|
364
|
+
|
|
365
365
|
Trait functions are covered in the section about [traits and instances](traits-and-instances)
|