-
Notifications
You must be signed in to change notification settings - Fork 0
/
Tiny Documentation.html
3863 lines (2690 loc) · 92.6 KB
/
Tiny Documentation.html
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html>
<head>
<h1>
The Tiny Programming Language</h1>
<style>
body {
font-family: "Open Sans", Helvetica, Arial, Sans-Serif;
}
table {
border: 1px solid black;
}
th {
padding: 10px;
text-align: center;
background-color: #e0e0e0;
}
td {
padding: 10px;
text-align: left;
vertical-align: top;
}
tr:nth-child(even) {background-color: #f2f2f2;}
</style>
</head>
<body>
<p>
Generated:
6/21/2023 10:51:02 AM
</p>
<h2>
Introduction</h2>
<p>
Tiny is a small function-oriented imperative dynamic language
written entirely in PowerShell. It has both named and anonymous
functions (lambdas) which are used extensively in list processing.
Tiny also supports regular expression matching on strings and
generalized pattern matching on lists and properties. Patterns can
also be used in function parameters allowing multiple dispatch (multimethods).
Tiny is expression-oriented where the value of a list of statements
is the value of the last statement in the list. The syntax of Tiny
is largely a subset of C# but with a 'match' statement inspired by
<a href="https://fsharp.org/">F# (fsharp)</a>
, lambda syntax inspired by
<a href="http://groovy-lang.org">Groovy</a>
and
<a href="https://kotlinlang.org">Kotlin</a>
and an object literal (dictionary) notation adopted from JavaScript
(In fact, a JSON file is a valid Tiny script).
Other influences include Poplog, Haskell, Elm and Elixir/Erlang.
The type system however, is essentially the unmodified PowerShell
"Extended Type System" (ETS).
Operator semantics are substantially informed by the host language
<a href="https://microsoft.com/powershell">PowerShell.</a>
The language and it's operators are case-insensitive by default but
case-sensitive operations can
be expressed using properly constructed regular expression literals. (see below.)
</p>
<p>
Tiny is implemented as a single monolithic PowerShell script.
To start the REPL, simply run './tiny.ps1' from inside a
<a href="https://github.com/powershell/powershell">PowerShell 6 (Core)</a>
session.
</p>
<h3>
Documentation and Tests</h3>
<p>
Much of the documentation, including the grammar for the language is embedded in
'tiny.ps1' as comments. The script ''tinydoc.tiny' will extract these comments and generate
an HTML page containing the documentation in ''Tiny Documentation.html''. To run the
script, start Tiny and do:
<pre> run 'tinydoc' {browser:true}</pre>
This command will generate the HTML file and then open it in a browser window.
</p>
<p>
Similarly, 'micro tests' (doc tests) are embedded in the implementing script as comments of the form:
<pre> #T <testName> <testText></pre>
In addition, the 'tests' subdirectory contains larger unit tests written in a Tiny DSL,
The script 'tinytest.tiny' will extract and run the micro tests as well as running
the unit tests in the 'tests' subdirectory. You can run this test from within Tiny as
follows:
<pre> run 'tinytest' {force:true}</pre>
Specifying 'force:true' will cause the test driver to also build the documentation.
By default, 'tinytest' will run in quiet mode. To see all of the tests being run,
invoke the script with the 'verbose' option as follows:
<pre> run 'tinytest' {verbose:true}</pre>
</p>
<h3>
Examples</h3>
<p>The following section provides a number of small examples illustrating the basic syntax and semantics of the Tiny language.</p>
<table width="100%">
<tr>
<th>
Description</th>
<th>
Example</th>
<th>
Notes</th>
</tr>
<tr>
<td>
Numeric Literals</td>
<td><pre>123
3.14
22.006e16
0xdeadbeef
1_000_000_000.00</pre></td>
<td>
Numeric literals without a decimal point are parsed as integers. With a decimal point,
they are parsed as doubles. Hex values start with '0x' and are also integers.
Numberic literals may optionally include the '_' character which is simply ignored.
</td>
</tr>
<tr>
<td>
String Literals and Interpolation</td>
<td><pre>"a double-quoted\nstring with escapes"
'a single-quoted string'
x = 1; "x is $x ${2+3}" == "x is 1 5"</pre></td>
<td>
Strings can have either single or double quotes.
Escape sequences are processed in double-quoted strings but
not in single-quoted strings. String interpolation is
also done in double-quoted strings such that "$var" is replaced by the string
value of variable "var" And "${expr}" is replaced by the result of the
evaluating the expression between the braces.
</td>
</tr>
<tr>
<td>
Regular Expression Literals</td>
<td><pre>r/abc/c
'abc' ~ r/abc/ == true
'abc' ~ r/Abc/c == false
'Abc' ~ r/Abc/c == true
'[123]' ~ r/^.([0-9]+).$/; matches[1] == '123'</pre></td>
<td>
Regular expression literals can be used with the regex operators or
in list or property pattern matching.
By default, all matches are case insensitive (default) but case sensitive
matches can be done by adding the 'c' trailing option e.g.
<pre>r/Abc/c</pre>
Regular expression literals compile the regular expressions while
the containing script is compiled so any syntax errors in a reguilar
expression literal will be caught at script compile time. As is the
case in PowerShell, a regular expression match sets a special variable
'matches' to the string elements matches by the expression as shown
in the last example in the examples box.
Note: string interpolation is not done in regular expression literals.
</td>
</tr>
<tr>
<td>
Variable and Function Names</td>
<td><pre>aVariable
a_variable
a123
a_function()
_
__a_var__</pre></td>
<td>
Variable, function or method names must start with a letter or '_'
followed by any number of letters, numbers or underbars. Variables must be
assigned before thay can be used otherwise an error will occur. The variables
'null' and '_' are special in that anything assigned to them is discarded.
'_' is commonly used in pattern matching as the 'dont't care' value. On the other
hand, using 'null' in a pattern matches the literal value null i.e. the pattern match
only succeeds if the corresponding value is null.
</td>
</tr>
<tr>
<td>
Pinned Variables</td>
<td><pre>myvar = 'def'; ['abc', 'def'] ~ 'abc'::^myvar;
[1, 2, 2, 1] ~ [a, b, ^b, ^a] # match a palindrome
fn startswith list val -> list ~ ^val::_
</pre></td>
<td>
Pinned variables (adapted from Elixir) are a special case of variables.
A pinned variable name starts with the '^' character e.g. '^myVar'. In
normal script contexts, a pinned variable is the same as a regular variable
i.e. '^abc' and 'abc' refer to exactly the same variable
However in list patterns they serve a special purpose. In list patterns, variables
are normally bound as part of the matching process. In other words, the
corresponding list element is assigned to the variable. However with
pinned variables, the variable's value is used in the matching process
instead of the variable being bound.
</td>
</tr>
<tr>
<td>
List Literal and the Range Operator</td>
<td><pre>[1, 2, 3, [4, 5], 10 .. 20];
[1, 2, 1..5..2, 5, 6] == [1, 2, 1, 3, 5, 5, 6];
[1, [2..5], 6] == [1, [2,3,4,5], 6]</pre></td>
<td>
Syntactically, list (array) literals are surrounded by square brackets -
'[' and ']' - and the individual elements of the list are separated by commas (',').
List elements can take any value expression as an element. Nested lists can also
be specified. The range operator '..' can be used in-line with an array literal.
In that case, the list produced by the operator is merged into the containing list.
The range operator can also be used in nested lists. List patterns can be used
to destructure list.
</td>
</tr>
<tr>
<td>
Object (Hashtable) Literals</td>
<td><pre>{a:1 'key with space':2+2 c:"hi"};
{a : foreach(i in 1..10) { i }};
o = {v:2 method:{x -> this.v * x}};
o.method(5) == 10
{} # empty object, NOT a empty lambda</pre></td>
<td>
Object literals are implemented as hashtables. Keys can be variable names or strings (but not
general expressions). Values can be expressions of single statements. If the value associated with
a key is a lambda then it can be invoked as a method using paretheses e.g. 'obj.method()'.
WHen invoked in this way, the containing object is available to the method body in
the 'this' variable. Members can be accessed with either array notation 'hashvar["a"]' or
property notation 'a.a'. If the value associated with the member is a lambda, then it will be
executed like a method: 'o.method(5)'.
</td>
</tr>
<tr>
<td>
Unary Operators</td>
<td><pre>true == ! false
! 0 == false
a = 13; -a == -13
+[1..10] == 55
+ ['a', 'b', 'c'] == 'abc'</pre></td>
<td>
As well as unary casts, Tiny has three other unary operators: '!' (boolean not),
'-' (unary minus) and '+' (unary plus). Unary plus is interesting: when
applied to a list of values, it will sum those values as shown in the examples column.
</td>
</tr>
<tr>
<td>
Arithmetic Operators</td>
<td><pre>2+2
'hi' + 'There'
10 * 3
16 / 6
9 % 2
[1..100] / 20</pre></td>
<td>
Tiny has the usual set of arithmetic operators: '+', '-', '*', '.', '/' and '%'.
Operators work on numbers, strings and arrays with conventional precedence rules.
'+' adds numbers and concatenates arrays and strings.
Dividing an array by a number will split the array into number
sized pieces. See the operators section for more details on semantics and
the complete list of operators.
</td>
</tr>
<tr>
<td>
Assignment Operators</td>
<td><pre>a = b = c = 2;
a += 3;
a -= 3
a *= 10
a /= 5</pre></td>
<td>
Tiny provides simple and compound assignment operators. Simple assignment can be used
to initialize a number of variables in a single statement.
</td>
</tr>
<tr>
<td>
Operators and Coercions</td>
<td><pre>2 + 2 == 4
2 + '2' == 4
'2' + 2 == '22'
[1] + 2 == [1, 2]
1 + [2] == [1, 2]</pre></td>
<td>
Because Tiny operators use the PowerShell operators (mostly), it inherits PowerShell's
behavior around type conversions. The left-hand rule generally applies: the type of the left
hand operand determines the type of the overall expression. However, with pure numeric operations,
the overall type of the expression is the that of the more precisely typed operand. Consequently
adding a double and an int will result in a double regardless of which side is which.
</td>
</tr>
<tr>
<td>
Type Literals</td>
<td><pre>1 is [<int>]
[<System.Diagnostics.StopWatch>].New()
'abc' is [<System.Collections.IEnumerable>]</pre></td>
<td>
Type literals use PowerShell's string-to-type conversion which includes
the default type accelerators as well as any namespaces in-scope in the implementing
script.
</td>
</tr>
<tr>
<td>
Type Casts</td>
<td><pre>[<int>] '123' is [<int>]
[<double>] (2 * 3)</pre></td>
<td>
Type casts use type literals as unary operators, converting the argument
value to the target type via PowerShell's conversion logic. This means that:
<pre>[<int>] "123"</pre>
succeeds and returns an integer. On the otherhand
<pre>[<int>] "abc"</pre>
results in an error. Casts can also be composed as follows:
<pre>[<int>] [<char>] "a" == 97</pre>
In this example, the first character of the string is turned into
a character and then into the integer '97'.
</td>
</tr>
<tr>
<td>
Regular Expression Operators</td>
<td><pre>strToMatch ~ regex
strToReplace -~ pattern
strToSplit /~ regex
strToMatchAll *~ regex</pre></td>
<td>
Tiny has the full set of regular expression operators: match (~), replace (-~),
split (/~) and match all (*~) which returns all matches in the target string.
Matching is done case-insensitively in the invariant locale unless a regex
literal with the 'c' option is used e.g,
<pre>'abc' ~ r/Abc/c == false</pre></td>
</tr>
<tr>
<td>
Spaces and Semicolons</td>
<td>
<pre>
# one expression - assigns result of a fn call
foo = bar {'Hi there'}
# two expressions - variable assignment + lambda
foo = bar; {'Hi there'}
# one expression - assign result of array index
foo = bar [2]
# two expressions - assignment + array literal
foo = bar; [2]
# One expression - assignment + cast
foo = [<int>] '123'
# Two expressions - assign type literal + string value
foo = [<int>]; '123'
</pre></td>
<td>
Tiny syntax is very free form. Semicolons are permitted between statements
but are usually not required.
For example, the following:
<pre>2+2 3*4 myfunc(2,3)</pre>
contains three distinct expressions without the use of semicolons. However semicolons
are needed in some cases such as those shown in the examples column.
</td>
</tr>
<tr>
<td>
Function Invocation</td>
<td><pre>
foo('abc', 12)
foo {it > 2}
foo {a: 1 b:2}
println 'Hello'
doit ('bob') {a:1 b:2}
count 'abc' + 3</pre></td>
<td>
In general, functions are invoked fairly conventionally with parentheses surrounding
the function arguments. However with special invocation, the function takes a single
argument that must be a lambda literal, object literal or string literal.
Parenthetical or string literal invocations may optionally be followed by
an object or lambda literal. If this is the case, the extra argument is available
inside the body of the function in the 'body' variable.
Special invocation forms are used in creating DSLs. See the Functions section
for a list of built-in functions.
</td>
</tr>
<tr>
<td>
Functions as infix operators</td>
<td><pre>fn avg x y -> (x+y)/2
2 `avg` 3 == 2.5</pre></td>
<td>
Like Haskell, Tiny allows you to invoke any function taking two arguments
as an infix (binary) operaor by surrounding the function name with
backticks (`). All functions invoked this way
share the same precedence which is the same as the precedence of
the range operator ('..').
</td>
</tr>
<tr>
<td>
Pattern Matching with List Patterns</td>
<td><pre>
a::b::c::_ = [1,2,3];
['foo','bob','smith'] ~ 'foo'::{it == 'bob'}::var;
[1, [20, [300]]] ~ a::[b::[c::_]::_]::_ </pre></td>
<td>
Where regular expressions are used to match strings, list patterns are used to match
lists. List patterns are composed of variables, which capture values, and
literals such as strings, numbers, etc that must be present in the target
list. Pattern elements are separated by the '::' operator. If a pattern
element is a lambda, that pattern will be evaluate to see if it matches using
the 'It' default variable. If the last element in the pattern is a variable,
it will have an array value even if the number of elements in the pattern is
the same as the number of elements in the target.
This is to facillitate certain recursive matching patterns.
If you want to ensure a scalar value in the element, you must add an extra
pattern element '_' which will succeed in the match and
be bound to an empty array. For example, in the following,
<pre>[1, 2] ~ a::b::_ ; (b is [<int>]) == true && b == 2</pre>
'b' will be bound to the scalar value 2.
</td>
</tr>
<tr>
<td>
Pattern Matching with Property Patterns</td>
<td><pre>
{:: a:1 b:b c: r/[a-z]/ ::}</pre></td>
<td>
As well as regular expressions and list patterns, Tiny supports a third kind
of pattern - property patterns - which are used to match against the properties
on objects (or dictionaries). Property patterns can contain the same pattern elements
as list patterns. The significant difference is that Property Patterns also allow you
to specify the name of the property to catch as a variable:
'<name>[:<valueToMatch>]'.
So a pattern element 'a' will simply capture the value of the property 'a' in a
variable called 'a'. A pattern element with a name and a regular expression value
like 'a:/r[0-9]' will match the value in the property 'a' only if that value matches
the regular expression. Property patterns work both against dictionary members
and object properties.
</td>
</tr>
<tr>
<td>
The 'if' Statement</td>
<td><pre>if (x > 15) {
println 'Greater'
}
elseif (x< 10) {
println 'Smaller'
}
else {
println 'Ok'
}
myvar = if (x % 2 == 0) { 'even' } else { 'odd' }
</pre></td>
<td>
For Tiny control statements (if/while/foreach), both the parentheses and braces are required.
In the 'if' statement, additional cases are handled with 'elseif' clauses. All Tiny
control statements, including 'if' statements are expressions with values that can be
assigned to variables. (Note that in many cases, it may be preferrable to use the 'match'
statement instead of 'if' for reasons of simplicitly and clarity of code.)
</td>
</tr>
<tr>
<td>
The 'while' Statement</td>
<td><pre>
i=0
while (i < 0) {
println('I is {0}', i)
i += 1
}
i = 0
list = while (i<10) { i += 1; i}
list == [1 .. 10]</pre></td>
<td>
The 'while' statement is also expression valued with the result being the result of the
statement in the statement list aggregated over the iterations. The second of the provided
examples shows how to generate a list of values with a while statement.
</td>
</tr>
<tr>
<td>
The 'foreach' statement</td>
<td><pre>foreach (i in [1 .. 10]) {
println('i is ' + i)
}
(foreach (i in 1..5) { i*2 }) == [2,4,6,8,10]</pre></td>
<td>
The 'foreach' statement iterates over a list of items, executing the statement
body once for each item. As always, the value of the foreach statement (expression)
is the value of the last statement in the body. This value is aggregated for all
iterations and returned as a collection as illustrated in the second example.
If the last statement in the block doesn't emit a value, then the overall value
of the 'foreach' statement will be null.
</td>
</tr>
<tr>
<td>
The 'match' statement</td>
<td><pre>match value
| 1 -> 'one'
| r/[a-z]/ -> 'a letter'
| {isnumber(it) && it % 2 == 0} -> 'even'
| -> 'default'</pre></td>
<td>
The match statement matches a value against a set of pattern/action clauses.
A clause begins with the or bar '|' followed by the pattern expression then an
arrow '->' and finally the action. There can be multiple patterns in
a clause which must be separated by the or bar '|'.
The first pattern that matches triggers the associated action which returns
it's value. Patterns can be literal values such as strings or integers,
regular expressions in which case a regex match is done, Tiny
patterns (e.g. 'a::b::c') in which case pattern matching is done or lambdas.
If the pattern is a lambda, the value being tested is available in the body
of the lambda in the 'it' variable.
Actions can be single expressions (including a single statement) or
a lambda which is executed rather than returned.
</td>
</tr>
<tr>
<td>
The 'matchlist' statement</td>
<td><pre>input = [['a', 2], ['b', 3], ['a', 3], ['c', 4]]
ehs = matchlist input| 'a'::n::_ -> n
ehs.Sum() == 5</pre></td>
<td>
The 'matchlist' statement behaves exactly like the 'match' statement
except it iterates over the value to test. This allows you to match
against a list of lists using pattern matching
in a very concise way. The 'matchist' statement returns a list
containing the result of all actions that have been executed. If
no actions execute, then it will return an empty list.
In the example shown, 'matchlist' iterates over
the list of lists, extracting
the second element from each candidate list where the first element is 'a'.
The results are aggregated into the
variable 'ehs' and then summed to produce 5.
</td>
</tr>
<tr>
<td>
The try/catch/finally Statement</td>
<td><pre>try {
1/0
}
catch {
println("exception was $it")
}
finally {
println('Done')
}
</pre></td>
<td>
Like many languages, Tiny uses exceptions for error handling and so has
the conventional try/catch/finally statement. A current limitation in the language
is that you cannot specify the type of exception to catch. (Internally all Tiny
exceptions are of type [<TinyException>] anyway so it doesn't really matter).
The actual exception instance is available in the 'catch' body through
the 'it' variable.
</td>
</tr>
<tr>
<td>
The 'return' Statement</td>
<td><pre>fn foo {1; return 12; 2} foo() == 12</pre></td>
<td>
Normally the return value from a function is the value of the last statement executed.
The return statement allows you to return a value from anywhere in a function
by exiting a function body early. This also applies to lambdas with the
special case that return, when used in a lambda that is invoked with the <b>Invoke()</b>
method will exit both the lambda and the calling function. This allows for block-like
return behavior in functions that take lambda arguments.
</td>
</tr>
<tr>
<td>
The 'break' Statement</td>
<td><pre>while (true) { break }</pre></td>
<td>
BUGBUGBUGBUG TBD
</td>
</tr>
<tr>
<td>
The 'continue' Statement</td>
<td><pre>foreach (i in 1..10) { continue }</pre></td>
<td>
BUGBUGBUGBUG TBD
</td>
</tr>
<tr>
<td>
Basic Function Definitions</td>
<td>
<pre>fn foo (x, y) {
println('x=' + x + ' y=' + y)
x+y
}</pre></td>
<td>
The return value of a function is the value of the last statement executed
but the return statement can optionally used.
Semicolons are permitted between statements but are rarely required.
</td>
</tr>
<tr>
<td>
"Relaxed" Function Definition</td>
<td>
<pre>fn foo x y {
println('x=' + x + ' y=' +y)
x+y
}</pre></td>
<td>
Parentheses around and commas between parameters can be omitted in function
definitions allowing for a 'relaxed' style that can be more readable.
</td>
</tr>
<tr>
<td>
Expression-bodied Functions.</td>
<td><pre>fn plus x y -> x+y # fn to add 2 numbers
# Returns true if the number is even
fn even n ->
if (n % 2 == 0) { true }
else {false}
# Returns a new hashtable
fn returnsAHash f l -> {
FirstName: f
LastName: l
}
</pre></td>
<td>
In expression-bodied functions, the body of the function is a single expression following
the '->' (It can also be a statement. As always, the result of the function is the result
of the last statement returned.) Note that a lambda specified after the '->' is treated as
the value to return not as the body of the function, This is different from the behaviour of
'-> { ... }' in a match statement where the lambda is treated as the body of the function.
</td>
</tr>
<tr>
<td>
Parameter Patterns</td>
<td><pre>fn foo [<int>] n -> 'n is an int'
fn foo 1 -> 'One'
fn isaNum r/[0-9]+/ -> true
fn hd x::_ -> x
fn tl _::xs -> xs
</pre></td>
<td>
As well as variables to bind, functions may also use "parameter patterns" which are
largely similar to patterns in the match statement. A parameter pattern can be as simple as
a type constraint on a parameter or it could be a literal value, a regex, a TinyList
or property pattern literal. If the parameter match succeeds, the function body is evaluated.
If the parameter match fails, then an error is raised. See "function sets" for an alternative
to this error behaviour.
</td>
</tr>
<tr>
<td>
Function Sets and Parameter Patterns (Multiple Dispatch))</td>
<td><pre>def fib 0 -> 1
def fib 1 -> 1
def fib n -> fib(n-1)+fib(n-2)</pre></td>
<td>
<p>Tiny functions can be defined in 'sets' using the 'def' keyword.This is a generalization of method overloads in object-oriented programming.When this keyword is used, the function definitions are aggregated rather thanreplaced. This, along with pattern matching in parameter specifications, allowsyou to avoid explicit conditional logic in many cases. In the example shown,there are three definitions - two that match constant values and a third thathandles the normal case of generating the Fibonocci sequence. </p><p>Function sets are matched in order,stopping after the first successful parameter match. Note that subnsequent'def's continue to accumulate until the definition is remove. This can bedone using the 'undef' statement.Arity or number of parameters, is also significant to in function selection.Two functions with the same name but differentarity are considered different functions in a function set.(Another term used to describe this feature is 'multimethods' or 'multiple dispatch'.)</p></td>
</tr>
<tr>
<td>
The 'undef' statement</td>
<td><pre>undef zork</pre></td>
<td>
The 'undef' keyword allows you to undefine a variable or function definition
at parse time rather than at run time. Since functions are bound at parse time,
this statement is most useful for dealing with functions, particularily
with function sets defined with the 'def' keyword. Since 'def' functions are always
added to the existing function set, you need a way to remove the current function set definition
before rebinding them. The 'undef' statement will do this.
</td>
</tr>
<tr>
<td>
Functions, Memoization and '_me'</td>
<td><pre>
def fact 0 -> 1
def fact n ->
if (_me.IsMemoized(n))
{ _me.GetMemoized(n) }
else
{ _me.Memoize(n, n * fact(n-1)) }
fact |> foreach { it.ClearMemoized() }
def fib 0 -> 1
def fib 1 -> 1
# Use overload that takes a computation (lambda) instead of a value
def fib n -> _me.Memoize(n, {fib(n-1) + fib(n-2)})</pre></td>
<td>
Memoization is a technique whereby future computations can be sped up by reusing previous
computations. In Tiny, to facillitate this, each function or lambda has an optional
'memo table' associated with it. This memo table (or rather methods for accessing this
storage table) are available on the lambda itself. The lambda object is available inside the lambda body
through the variable '_me'. The memoization method set consists of four methods+2 overloads:
<pre> _me.IsMemoized(object) # Returns true if the object has been memoized</pre>
<pre> _me.GetMemoized(object), _me.GetMemoized() # return the whole table</pre>
<pre> _me.Memoize(object, value), _me.Memoize(object, {...}) # Get the memoized value</pre>
<pre> _me.ClearMemoize() # Clear the memo table for a lambda.</pre>
The memo table is created the first time an object/value pair is memoized. The code
in the example column shows the canonical patterns for using these methods.
The example using the overload of Memoize() that takes a lambda representing the
computation is the simplest pattern but has the additional overhead of a second
lambda dispatch.
</td>
</tr>
<tr>
<td>
Example: Recursive Fibinocci function using the 'match' statement.</td>
<td>
<pre>
fn fib n ->
match x
| 0 -> 1
| 1 -> 1
| -> fib(n-1) + fib(n-2)
</pre></td>
<td>
The match statement matches its argument against the values in each of the case clauses.
Match elements can be literal strings or numbers, types (in which case the target objects type is
matched), regular expressions or lambdas. The associated actions can be expressions or statements.
The default clause is indicated by including no match expression. The result value is the result
of executing the action for the matching clause.
</td>
</tr>
<tr>
<td>
Example: Recursive function set to compute the length of a list.</td>
<td>
<pre>
fn len list ->
match list
| [] -> 0
| _::tail -> 1 + len(tail)
</pre></td>
<td>
While this is an extremely inefficient way to get the length of the list, it illustrates
the use of pattern matching with lists in the 'match' statement.
</td>
</tr>
<tr>
<td>
Example: Recursive function set to compute the length of a list using parameter patterns.</td>
<td><pre>def len [] -> 0
def len _::tail -> 1 + len(tail)</pre></td>
<td>
As with the previous example, this is another inefficient way to get the
length of the list. This example illustrates the use of parameter patterns
in deconstructing lists. The first 'def' handles the empty-list case
and returns 0. The second 'def' handles the non-empty list case,
using '_' to match the head of the list (since we don't care what
it is, only that it exists) and 'tail' to capture the rest of the
list. It adds 1 to the result of calling
'len' on the list tail giving the length of the overall list.
</td>
</tr>
<tr>
<td>
Variable and Function Scopes</td>
<td>
<pre>__global.AGlobalVariable = 123
__parent.AVariableInTheParentScope = 456
x = 123; __current.X = x</pre></td>
<td>
Tiny currently uses dynamic scoping just like PowerShell: variables are inherited
from the caller's scope, variable writes are always done in the local scope.
Variables must assigned before they can be used. The current, global and parent scopes
are accessible through the variables '__current', '__global' and '__parent'.
Functions are bound in the same namespace an regular variables. In fact, a function
is simply a variable containing an 'invokable'
object such as a Tiny lambda, a MethodInfo object or a PowerShell scriptblock so
a function definition such as:
<pre>fn add x y -> x+y</pre>
is essentially the same as:
<pre>add = {x, y -> x+y}</pre>
except that functions bound with the 'fn' keyword are bound at parse time
whereas assignment occurs at runtime.
</td>
</tr>
<tr>
<td>
Lambda Literals</td>