@mojir/lits 2.1.36 → 2.1.38

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/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # Lits
2
2
 
3
- Lits is a lexically scoped pure functional language with algebraic notation. It combines the power of functional programming with an intuitive, readable syntax that makes complex operations simple and expressive.
3
+ A functional language with algebraic notation and JavaScript interoperability.
4
4
 
5
5
  Try it in the [Lits Playground](https://mojir.github.io/lits/).
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **Pure functional language** - Variables cannot be changed, ensuring predictable behavior and easier reasoning about code
10
+ - **Expression-based syntax** - Everything in Lits is an expression that returns a value; there are no statements, making the language highly composable and consistent
11
+ - **Fully serializable** - Every value returned from Lits evaluation, including functions and regexps, is serializable as JSON
10
12
  - **JavaScript interoperability** - JavaScript values and functions can easily be exposed in Lits
11
13
  - **First-class functions** - Functions are treated as values that can be passed to other functions
12
14
  - **Algebraic notation** - All operators can be used as functions, and functions that take two parameters can be used as operators
@@ -56,7 +58,7 @@ The REPL provides an interactive environment where you can experiment with Lits
56
58
  Here's a simple example to get you started:
57
59
 
58
60
  ```lits
59
- // Defining a function
61
+ // Defining a function - note that everything returns a value
60
62
  let square = x -> x * x;
61
63
 
62
64
  // Using the function
@@ -68,10 +70,33 @@ let squares = [1, 2, 3, 4, 5] map square;
68
70
  // => [1, 4, 9, 16, 25]
69
71
 
70
72
  // Using operator as a function
71
- let sum = +(1, 2, 3, 4, 5);
73
+ +(1, 2, 3, 4, 5);
72
74
  // => 15
73
75
  ```
74
76
 
77
+ ## Expression-Based Language
78
+
79
+ In Lits, everything is an expression that evaluates to a value. This means:
80
+
81
+ ```lits
82
+ // Conditional expressions always return a value
83
+ let a = 10;
84
+ let result = if a > 0 then "positive" else "non-positive" end;
85
+
86
+ // Function definitions are expressions that return the function
87
+ let add = (a, b) -> a + b;
88
+
89
+ // Even variable bindings return the bound value
90
+ let x = let y = 5; // x becomes 5
91
+
92
+ // Blocks are expressions - they return the last expression's value
93
+ let value = {
94
+ let temp = 42;
95
+ temp * 2 + 1 // => 85
96
+ };
97
+ ```
98
+ This expression-based design makes Lits highly composable and eliminates the statement/expression distinction found in many other languages.
99
+
75
100
  ## Basic Syntax
76
101
 
77
102
  ### Data Types
@@ -87,24 +112,231 @@ let sum = +(1, 2, 3, 4, 5);
87
112
 
88
113
  // Strings
89
114
  "Hello, world!";
115
+ "String with \"escapes\"";
90
116
 
91
117
  // Booleans
92
118
  true;
93
119
  false;
94
120
 
95
- // Null
121
+ // Functions
122
+ (x -> x * 2); // Anonymous function
123
+ let add = (a, b) -> a + b; // Named function
124
+
125
+ // Regular expressions
126
+ #"[a-z]+";
127
+ #"\d{3}-\d{3}-\d{4}";
128
+
129
+ // null
96
130
  null;
131
+ ```
97
132
 
98
- // Arrays
99
- [1, 2, 3, 4];
133
+ #### Arrays (General Collections)
134
+
135
+ Arrays are the primary collection type in Lits, supporting mixed data types:
136
+
137
+ ```lits
138
+ // Basic arrays
139
+ [1, 2, 3, 4, 5];
140
+ ["apple", "banana", "orange"];
141
+ [true, 42, "mixed types"];
142
+
143
+ // Nested arrays
144
+ [[1, 2], [3, 4], [5, 6]];
145
+ [{name: "Alice"}, {name: "Bob"}];
146
+
147
+ // Array operations
148
+ let numbers = [1, 2, 3, 4, 5];
149
+ numbers[0]; // => 1 (indexing)
150
+ count(numbers); // => 5 (length)
151
+ first(numbers); // => 1
152
+ last(numbers); // => 5
153
+ rest(numbers); // => [2, 3, 4, 5]
154
+
155
+ // Functional array operations
156
+ numbers map -> $ * 2; // => [2, 4, 6, 8, 10]
157
+ numbers filter odd?; // => [1, 3, 5]
158
+ ```
159
+
160
+ #### Vectors (Number Arrays)
161
+
162
+ A vector is simply a non-empty array containing only numbers. The `vec:` namespace provides mathematical operations specifically for these number arrays:
163
+
164
+ ```lits
165
+ // Vectors are just number arrays
166
+ [1, 2, 3, 4, 5]; // This is a vector
167
+ [3.14, 2.71, 1.41]; // This is also a vector
168
+ [1, "hello", 3]; // This is NOT a vector (mixed types)
169
+ []; // This is NOT a vector (empty)
170
+
171
+ // Vector creation functions
172
+ vec:zeros(5); // => [0, 0, 0, 0, 0]
173
+ vec:ones(3); // => [1, 1, 1]
174
+ vec:linspace(0, 10, 5); // => [0, 2.5, 5, 7.5, 10]
175
+ vec:fill(4, 3.14); // => [3.14, 3.14, 3.14, 3.14]
176
+ vec:generate(5, -> $ * 2); // => [0, 2, 4, 6, 8]
177
+
178
+ // Vector mathematical operations (use lin: namespace for vector math)
179
+ lin:dot([1, 2, 3], [4, 5, 6]); // => 32 (dot product)
180
+ lin:euclidean-norm([3, 4]); // => 5.0 (Euclidean norm/magnitude)
181
+ lin:normalize([3, 4]); // => [0.6, 0.8] (unit vector)
182
+ lin:euclidean-distance([0, 0], [3, 4]); // => 5.0 (Euclidean distance)
183
+
184
+ // Vector statistical operations
185
+ vec:sum([1, 2, 3, 4]); // => 10
186
+ vec:mean([1, 2, 3, 4]); // => 2.5
187
+ vec:median([1, 2, 3, 4, 5]); // => 3
188
+ vec:stdev([1, 2, 3, 4]); // => 1.29... (standard deviation)
189
+ vec:variance([1, 2, 3, 4]); // => 1.67... (variance)
190
+
191
+ // Vector analysis
192
+ vec:min([3, 1, 4, 1, 5]); // => 1
193
+ vec:max([3, 1, 4, 1, 5]); // => 5
194
+ vec:min-index([3, 1, 4]); // => 1 (index of minimum)
195
+ vec:max-index([3, 1, 4]); // => 2 (index of maximum)
196
+
197
+ // Cumulative operations
198
+ vec:cumsum([1, 2, 3, 4]); // => [1, 3, 6, 10]
199
+ vec:cumprod([1, 2, 3, 4]); // => [1, 2, 6, 24]
200
+
201
+ // Vector predicates
202
+ vec:increasing?([1, 1, 2, 3, 4]); // => true
203
+ vec:strictly-increasing?([1, 1, 2, 3, 4]); // => true
204
+
205
+ // Structural equality works with all vectors
206
+ [1, 2, 3] == [1, 2, 3]; // => true
207
+ [1, 2] == [1, 2, 3]; // => false
208
+ ```
209
+
210
+ #### Matrices (2D Vectors)
211
+
212
+ A matrix is a 2D array where each row is a vector (non-empty array of numbers) and all rows have the same length. The `mat:` namespace provides linear algebra operations for these structures:
213
+
214
+ ```lits
215
+ // Matrices are 2D number arrays with consistent row lengths
216
+ [[1, 2], [3, 4]]; // This is a 2x2 matrix
217
+ [[1, 2, 3], [4, 5, 6]]; // This is a 2x3 matrix
218
+ [[1, 2], [3, 4, 5]]; // This is NOT a matrix (inconsistent row length)
219
+ [[1, "hello"], [3, 4]]; // This is NOT a matrix (contains non-numbers)
220
+ [[]]; // This is NOT a matrix (contains empty row)
221
+
222
+ // Basic matrix operations
223
+ let matrixA = [[1, 2], [3, 4]];
224
+ let matrixB = [[5, 6], [7, 8]];
225
+
226
+ mat:mul(matrixA, matrixB); // => [[19, 22], [43, 50]] (multiplication)
227
+ mat:det(matrixA); // => -2 (determinant)
228
+ mat:inv(matrixA); // => [[-2, 1], [1.5, -0.5]] (inverse)
229
+ mat:trace(matrixA); // => 5 (trace - sum of diagonal)
230
+
231
+ // Matrix construction
232
+ mat:hilbert(3); // => 3x3 Hilbert matrix
233
+ mat:vandermonde([1, 2, 3]); // => Vandermonde matrix from vector
234
+ mat:band(4, 1, 1); // => 4x4 band matrix
235
+
236
+ // Matrix properties and predicates
237
+ mat:symmetric?([[1, 2], [2, 1]]); // => true
238
+ mat:invertible?([[1, 2], [3, 4]]); // => true
239
+ mat:square?([[1, 2], [3, 4]]); // => true
240
+ mat:diagonal?([[1, 0], [0, 2]]); // => true
241
+ mat:identity?([[1, 0], [0, 1]]); // => true
242
+
243
+ // Advanced matrix operations
244
+ mat:adj(matrixA); // => [[4, -2], [-3, 1]] (adjugate)
245
+ mat:cofactor(matrixA); // => cofactor matrix
246
+ mat:minor(matrixA, 0, 1); // => minor by removing row 0, col 1
247
+ mat:frobenius-norm(matrixA); // => Frobenius norm
248
+ mat:1-norm(matrixA); // => 1-norm (max column sum)
249
+ mat:inf-norm(matrixA); // => infinity norm (max row sum)
250
+ mat:max-norm(matrixA); // => max norm (largest absolute element)
251
+
252
+ // Matrix analysis
253
+ mat:rank(matrixA); // => matrix rank
254
+ ```
255
+
256
+ #### Objects (Maps)
100
257
 
101
- // Objects
258
+ Objects store key-value pairs:
259
+
260
+ ```lits
261
+ // Object creation
102
262
  { name: "John", age: 30 };
263
+ { "key with spaces": "value", count: 42 };
103
264
 
104
- // Regular expressions
105
- #"pattern";
265
+ // Nested objects
266
+ {
267
+ person: { name: "Alice", age: 25 },
268
+ scores: [95, 87, 92],
269
+ active: true
270
+ };
271
+
272
+ // Object operations
273
+ let user = { name: "Bob", age: 30, city: "NYC" };
274
+ get(user, "name"); // => "Bob"
275
+ assoc(user, "age", 31); // => new object with age updated
276
+ dissoc(user, "city"); // => new object without city
277
+ keys(user); // => ["name", "age", "city"]
278
+ vals(user); // => ["Bob", 30, "NYC"]
279
+ ```
280
+
281
+ #### Type Predicates
282
+
283
+ Lits provides predicate functions to check data types at runtime:
284
+
285
+ ```lits
286
+ // Basic type predicates
287
+ number?(42); // => true
288
+ string?("hello"); // => true
289
+ boolean?(true); // => true
290
+ function?(x -> x * 2); // => true
291
+ regexp?(#"[a-z]+"); // => true
292
+ array?([1, 2, 3]); // => true
293
+ object?({name: "Alice"}); // => true
294
+ null?(null); // => true
295
+
296
+ // Specialized array predicates
297
+ vector?([1, 2, 3]); // => true (non-empty number array)
298
+ vector?([1, "hello", 3]); // => false (mixed types)
299
+ vector?([]); // => false (empty)
300
+
301
+ matrix?([[1, 2], [3, 4]]); // => true (2D number array, consistent rows)
302
+ matrix?([[1, 2], [3]]); // => false (inconsistent row lengths)
303
+ matrix?([[]]); // => false (contains empty row)
304
+
305
+ // Collection predicates
306
+ seq?([1, 2, 3]); // => true (sequences: strings and arrays)
307
+ seq?("hello"); // => true
308
+ seq?({a: 1}); // => false
309
+
310
+ coll?([1, 2, 3]); // => true (collections: strings, arrays, objects)
311
+ coll?("hello"); // => true
312
+ coll?({a: 1}); // => true
313
+ coll?(42); // => false
106
314
  ```
107
315
 
316
+ #### Type Hierarchy
317
+
318
+ The type predicates follow a logical hierarchy:
319
+
320
+ ```lits
321
+ // If something is a matrix, it's also a vector and an array
322
+ let mat = [[1, 2], [3, 4]];
323
+ matrix?(mat); // => true
324
+ vector?(mat); // => true (matrix is a special vector)
325
+ array?(mat); // => true (vector is a special array)
326
+
327
+ // If something is a vector, it's also an array
328
+ let vec = [1, 2, 3];
329
+ vector?(vec); // => true
330
+ array?(vec); // => true
331
+
332
+ // But not all arrays are vectors
333
+ let arr = [1, "hello", 3];
334
+ array?(arr); // => true
335
+ vector?(arr); // => false (contains non-numbers)
336
+ ```
337
+
338
+ Each data type is immutable by design - operations return new values rather than modifying existing ones, ensuring predictable behavior and easier reasoning about code.
339
+
108
340
  ### Mathematical Constants
109
341
 
110
342
  Lits provides predefined mathematical constants:
@@ -192,7 +424,7 @@ let {
192
424
  settings = { theme: "light" },
193
425
  scores as userScores = [],
194
426
  ...others
195
- } = { name: "Sam", profile: { contact: {} }};
427
+ } = { name: "Sam", profile: { contact: {} } };
196
428
  // userName => "Sam", age => 0, userEmail => "none", etc.
197
429
  ```
198
430
 
@@ -214,11 +446,15 @@ let { name, scores: [one, two] } = { name: "Uma", scores: [85, 92] };
214
446
  // Array destructuring
215
447
  let [, , a, b] = [1, 2, 3, 4];
216
448
  // a => 3, b => 4
449
+ ```
217
450
 
451
+ ```lits
218
452
  // Array destructuring with defaults
219
453
  let [one, two = 2] = [1];
220
454
  // one => 1, two => 2
455
+ ```
221
456
 
457
+ ```lits
222
458
  // Skipping elements
223
459
  let [x, , z] = [1, 2, 3];
224
460
  // x => 1, z => 3
@@ -230,11 +466,15 @@ let [x, , z] = [1, 2, 3];
230
466
  // Array rest pattern
231
467
  let [head, ...tail] = [1, 2, 3, 4];
232
468
  // head => 1, tail => [2, 3, 4]
469
+ ```
233
470
 
471
+ ```lits
234
472
  // Object rest pattern
235
473
  let { name, ...otherProps } = { name: "John", age: 30, city: "NYC" };
236
474
  // name => "John", otherProps => { age: 30, city: "NYC" }
475
+ ```
237
476
 
477
+ ```lits
238
478
  // Empty rest patterns
239
479
  let [only, ...empty] = [1];
240
480
  // only => 1, empty => []
@@ -247,18 +487,24 @@ let [only, ...empty] = [1];
247
487
  let greet = ({ name }) -> "Hello, " ++ name;
248
488
  greet({ name: "Pat" });
249
489
  // => "Hello, Pat"
490
+ ```
250
491
 
492
+ ```lits
251
493
  // With defaults in parameters
252
494
  let greet2 = ({ name = "friend" }) -> "Hello, " ++ name;
253
495
  greet2({});
254
496
  // => "Hello, friend"
497
+ ```
255
498
 
499
+ ```lits
256
500
  // Nested parameter destructuring
257
501
  let processUser = ({ profile: { name, age }}) ->
258
502
  name ++ " is " ++ str(age);
259
503
  processUser({ profile: { name: "Quinn", age: 29 }});
260
504
  // => "Quinn is 29"
505
+ ```
261
506
 
507
+ ```lits
262
508
  // Array parameter destructuring
263
509
  let processCoords = ([x, y]) -> x + y;
264
510
  processCoords([3, 4]);
@@ -299,56 +545,59 @@ let factorial = n ->
299
545
  #### If/Unless
300
546
 
301
547
  ```lits
302
- let x = !:random-int(0, 20); // Random number between 0 and 19
548
+ let x = 15; // Fixed value for compilation
303
549
 
304
550
  if x > 10 then
305
551
  "large"
306
552
  else
307
553
  "small"
308
554
  end;
309
- // => "large" (if x > 10) or "small" (if x <= 10)
555
+ // => "large"
310
556
 
311
557
  // If without else returns null
312
558
  if false then "never" end;
313
559
  // => null
314
560
 
315
561
  // Unless (inverted if)
316
- let y = !:random-int(0, 20);
562
+ let y = 8;
317
563
  unless y > 10 then
318
564
  "small"
319
565
  else
320
566
  "large"
321
567
  end;
322
- // => "small" (if y <= 10) or "large" (if y > 10)
568
+ // => "small"
323
569
  ```
324
570
 
325
571
  #### Cond
326
572
 
327
573
  ```lits
328
- let x = !:random-int(0, 20); // Random number between 0 and 19
574
+ let x = 12;
329
575
 
330
576
  // Multi-branch conditional
331
577
  cond
332
578
  case x < 5 then "small"
333
579
  case x < 10 then "medium"
334
580
  case x < 15 then "large"
335
- end ?? "extra large";
336
- // Tests conditions sequentially, returns first truthy match
581
+ case true then "extra large" // default case
582
+ end;
583
+ // => "large"
337
584
 
338
585
  // Cond with complex conditions
339
- let urgent = !:random-int(0, 2) == 1;
340
- let important = !:random-int(0, 2) == 1;
586
+ let urgent = true;
587
+ let important = false;
341
588
  let priority = cond
342
589
  case urgent && important then "critical"
343
590
  case urgent then "high"
344
591
  case important then "medium"
345
- end ?? "low";
592
+ case true then "low"
593
+ end;
594
+ // => "high"
346
595
  ```
347
596
 
348
597
  #### Switch
349
598
 
350
599
  ```lits
351
- let x = !:random-int(0, 3); // Random number between 0 and 2
600
+ let x = 1;
352
601
 
353
602
  // Switch on value
354
603
  switch x
@@ -356,19 +605,20 @@ switch x
356
605
  case 1 then "one"
357
606
  case 2 then "two"
358
607
  end;
359
- // => "zero" (if x = 0), "one" (if x = 1), etc., or null if no match
608
+ // => "one"
360
609
 
361
610
  // Switch with multiple cases
362
611
  let userInput = "help";
363
- let exit = -> "exiting";
364
- let showHelp = -> "showing help";
365
- let saveData = -> "saving data";
612
+ let exit = () -> "exiting";
613
+ let showHelp = () -> "showing help";
614
+ let saveData = () -> "saving data";
366
615
 
367
616
  switch userInput
368
617
  case "quit" then exit()
369
618
  case "help" then showHelp()
370
619
  case "save" then saveData()
371
620
  end;
621
+ // => "showing help"
372
622
  ```
373
623
 
374
624
  ### Loops and Iteration
@@ -495,7 +745,7 @@ let parseData = () -> { value: 42 };
495
745
  let process = (val) -> val * 2;
496
746
  try
497
747
  let { value } = parseData();
498
- process(value);
748
+ process(value)
499
749
  catch
500
750
  "Using default value"
501
751
  end;
@@ -553,10 +803,25 @@ let processData = (data) -> data map -> $ * 2;
553
803
  let processed = processData(data);
554
804
  write!("Process completed");
555
805
  processed
556
- };
806
+ }
557
807
  ```
558
808
 
559
- ### Array and Object Spread
809
+ ### Array and Object Construction
810
+
811
+ #### Array Construction
812
+
813
+ ```lits
814
+ // Array literal
815
+ [1, 2, 3, 4];
816
+
817
+ // Array function
818
+ array(1, 2, 3, 4);
819
+
820
+ // With spread
821
+ let small-set = [3, 4, 5];
822
+ [1, 2, ...small-set, 6];
823
+ // => [1, 2, 3, 4, 5, 6]
824
+ ```
560
825
 
561
826
  #### Array Spread
562
827
 
@@ -572,6 +837,39 @@ let stop = [5, 6];
572
837
  let result = [...start, ...middle, ...stop];
573
838
  ```
574
839
 
840
+ #### Object Construction
841
+
842
+ ```lits
843
+ // Object literal with static keys
844
+ { name: "John", age: 30 };
845
+
846
+ // Object literal with dynamic keys using bracket notation
847
+ let keyName = "dynamic";
848
+ { [keyName]: "value", ["computed" ++ "Key"]: 42 };
849
+ // => { dynamic: "value", computedKey: 42 }
850
+
851
+ // Object function
852
+ object("name", "John", "age", 30);
853
+
854
+ // With spread
855
+ let defaults = { type: "Person", active: true };
856
+ {
857
+ ...defaults,
858
+ name: "John",
859
+ age: 30
860
+ };
861
+ // => { type: "Person", active: true, name: "John", age: 30 }
862
+
863
+ // Combining static and dynamic keys
864
+ let propName = "score";
865
+ {
866
+ id: 123,
867
+ [propName]: 95,
868
+ ["level" ++ "Number"]: 5
869
+ };
870
+ // => { id: 123, score: 95, levelNumber: 5 }
871
+ ```
872
+
575
873
  #### Object Spread
576
874
 
577
875
  ```lits
@@ -623,22 +921,21 @@ true || "never reached"; // => true
623
921
 
624
922
  ```lits
625
923
  // Null coalescing operator
626
- null ?? "default"; // => "default"
627
- undefined-var ?? "default"; // => "default"
628
- 0 ?? "default"; // => 0 (only null/undefined are coalesced)
629
- false ?? "default"; // => false
630
- "" ?? "default"; // => ""
924
+ null ?? "default"; // => "default"
925
+ 0 ?? "default"; // => 0 (only null/undefined are coalesced)
926
+ false ?? "default"; // => false
927
+ "" ?? "default"; // => ""
631
928
  ```
632
929
 
633
930
  ### Ternary Operator
634
931
 
635
932
  ```lits
636
933
  // Conditional expression
637
- let age = !:random-int(10, 60);
934
+ let age = 25;
638
935
  let result = age >= 18 ? "adult" : "minor";
639
936
 
640
937
  // Nested ternary
641
- let score = !:random-int(0, 100);
938
+ let score = 85;
642
939
  let category = score >= 90 ? "A" : score >= 80 ? "B" : "C";
643
940
 
644
941
  // With complex expressions
@@ -647,36 +944,396 @@ let hasPermission = () -> true;
647
944
  let status = isLoggedIn() && hasPermission() ? "authorized" : "unauthorized";
648
945
  ```
649
946
 
650
- ## Operators and Precedence
947
+ ## Variable Names
948
+
949
+ Lits is generous with variable naming conventions, allowing a wide range of characters that would be invalid in many other programming languages.
950
+
951
+ ### Basic Rules
952
+
953
+ Variable names in Lits can contain almost any character except for a small set of reserved symbols. The only restrictions are:
954
+
955
+ **Illegal characters anywhere in a variable name:**
956
+ - Parentheses: `(` `)`
957
+ - Brackets: `[` `]`
958
+ - Braces: `{` `}`
959
+ - Quotes: `'` `"` `` ` ``
960
+ - Punctuation: `,` `.` `;`
961
+ - Whitespace: spaces, newlines, tabs
962
+
963
+ **Additional restrictions for the first character:**
964
+ - Cannot start with digits `0-9`
965
+
966
+ ### Unicode and Emoji Support
967
+
968
+ Beyond these minimal restrictions, Lits supports Unicode characters, including emojis, in variable names:
969
+
970
+ ```lits
971
+ // Unicode characters are welcome
972
+ let résultat = 42;
973
+ let naïve = "simple approach";
974
+ let coöperation = "working together";
975
+
976
+ // Emojis work too!
977
+ let 😅 = "grinning face with sweat";
978
+ let 🚀 = "rocket ship";
979
+ let result = 😅 ++ " " ++ 🚀;
980
+ // => "grinning face with sweat rocket ship"
981
+ ```
982
+
983
+ ### Quoted Variable Names
984
+
985
+ For cases where you need to use the normally illegal characters in variable names, Lits supports quoted variable names using single quotes:
986
+
987
+ ```lits
988
+ // Variables with spaces and special characters
989
+ let 'A strange variable' = 42;
990
+ let 'user.name' = "John Doe";
991
+ let 'data[0]' = "first element";
992
+ let 'function()' = "callable";
993
+
994
+ // Access them the same way
995
+ 'A strange variable' + 8;
996
+ // => 50
997
+ ```
998
+
999
+ ### Practical Examples
1000
+
1001
+ Here are some examples showcasing the flexibility of Lits variable naming:
1002
+
1003
+ ```lits
1004
+ // Mathematical notation with Greek letters (avoiding reserved symbols)
1005
+ let α = 0.5;
1006
+ let β = 1.2;
1007
+ let γ = 2.0;
1008
+ let Δ = β - α;
1009
+
1010
+ // Descriptive names with special characters
1011
+ let user-name = "alice";
1012
+ let is-valid? = true;
1013
+ let counter! = 0;
1014
+
1015
+ // Mixed styles
1016
+ let dataSet₁ = [1, 2, 3];
1017
+ let dataSet₂ = [4, 5, 6];
1018
+ let 🔧config = { debug: true };
1019
+ ```
1020
+
1021
+ ### Important: Operator Spacing
651
1022
 
652
- Lits follows a clear operator precedence hierarchy. Understanding precedence helps you write expressions that behave as expected:
1023
+ Due to Lits' flexible variable naming, **operators must be separated by whitespace**. This is crucial to understand:
653
1024
 
654
- ### Precedence Table (Highest to Lowest)
1025
+ ```lits
1026
+ // This is a variable name, NOT addition!
1027
+ let x+1 = 42;
1028
+ let result1 = x+1; // => 42
1029
+
1030
+ // To perform addition, use spaces
1031
+ let x = 5;
1032
+ let result2 = x + 1; // => 6
1033
+
1034
+ // More examples of what looks like operations but are actually variable names
1035
+ let a-b = "subtraction variable";
1036
+ let c*d = "multiplication variable";
1037
+ let e/f = "division variable";
1038
+ let g<h = "comparison variable";
1039
+
1040
+ // To use these as actual operations, add spaces
1041
+ let a = 10;
1042
+ let b = 3;
1043
+ let a-sum = a + b; // Addition
1044
+ let a-diff = a - b; // Subtraction
1045
+ let a-prod = a * b; // Multiplication
1046
+ let a-quot = a / b; // Division
1047
+ let a-comp = a < b; // Comparison
1048
+ ```
1049
+
1050
+ Without whitespace, Lits treats the entire sequence as a single variable identifier. This applies to all operators, including comparison operators, logical operators, and arithmetic operators.
1051
+
1052
+ This flexibility allows for expressive and readable code while maintaining the functional programming paradigm that Lits embodies.
1053
+ ## Operators and Functions
1054
+
1055
+ ### Algebraic Notation
1056
+
1057
+ All functions that take two parameters can be used as operators:
1058
+
1059
+ ```lits
1060
+ // As a function
1061
+ max(5, 10); // => 10
1062
+
1063
+ // As an operator
1064
+ 5 max 10; // => 10
1065
+ ```
1066
+
1067
+ All operators can be used as functions:
1068
+
1069
+ ```lits
1070
+ // As an operator
1071
+ 5 + 3; // => 8
1072
+
1073
+ // As a function
1074
+ +(5, 3); // => 8
1075
+
1076
+ // Partial application with underscore placeholder
1077
+ let add5 = +(5, _);
1078
+ add5(3); // => 8
1079
+
1080
+ // Multiple placeholders
1081
+ let subtractTwoValues = -(100, _, _);
1082
+ subtractTwoValues(4, 3); // => 93
1083
+
1084
+ // Single placeholder in different positions
1085
+ let subtract = -(_, 2);
1086
+ subtract(10); // => 8
1087
+
1088
+ let divide = /(10, _);
1089
+ divide(2); // => 5
1090
+ ```
1091
+
1092
+ ### Data Types as Functions
1093
+
1094
+ Lits allows arrays, objects, numbers, and strings to be used as functions. This creates elegant, flexible code where data structures become accessors.
1095
+
1096
+ #### Arrays and Numbers as Index Accessors
1097
+
1098
+ Arrays can be called with an index to get an element, and numbers can be called with collections to access that index:
1099
+
1100
+ ```lits
1101
+ let arr = [10, 20, 30, 40];
1102
+
1103
+ // Array as function (accessing by index)
1104
+ arr(0); // => 10
1105
+ arr(2); // => 30
1106
+
1107
+ // Number as function (accessing array at that index)
1108
+ 2(arr); // => 30 (same as arr(2))
1109
+ 0(arr); // => 10 (same as arr(0))
1110
+ ```
655
1111
 
656
- 1. **Function calls** - `fn(args)`
657
- 2. **Array/Object access** - `arr[index]`, `obj.property`
658
- 3. **Unary operators** - `not`, `!`, `-` (negation)
659
- 4. **Exponentiation** - `^` (right-associative)
660
- 5. **Multiplication, Division, Modulo** - `*`, `/`, `%`
661
- 6. **Addition, Subtraction** - `+`, `-`
662
- 7. **String concatenation** - `++`
663
- 8. **Comparison operators** - `<`, `>`, `<=`, `>=`
664
- 9. **Equality operators** - `==`, `!=`, `identical?`
665
- 10. **Logical AND** - `&&`
666
- 11. **Logical OR** - `||`
667
- 12. **Null coalescing** - `??`
668
- 13. **Ternary conditional** - `condition ? true-value : false-value`
1112
+ #### Strings and Numbers for Character Access
669
1113
 
670
- ### Examples
1114
+ Similar to arrays, strings support indexed access in both directions:
671
1115
 
672
1116
  ```lits
673
- // Comparison and logical operators
674
- 5 > 3 && 2 < 4; // => true
675
- 5 > 3 || 2 > 4; // => true
1117
+ let name = "Albert";
676
1118
 
677
- // Ternary has low precedence
678
- let x = !:random-int(0, 10);
679
- x + 3 > 4 ? 1 : 0; // => (x + 3) > 4 ? 1 : 0
1119
+ // String as function (accessing character by index)
1120
+ name(0); // => "A"
1121
+ name(2); // => "b"
1122
+
1123
+ // Number as function (accessing string at that index)
1124
+ 2(name); // => "b" (same as name(2))
1125
+ 4(name); // => "r" (same as name(4))
1126
+ ```
1127
+
1128
+ #### Objects and Strings as Property Accessors
1129
+
1130
+ Objects can be called with property names, and strings can be called with objects to access properties:
1131
+
1132
+ ```lits
1133
+ let person = { foo: 1, bar: 2, name: "John" };
1134
+
1135
+ // Object as function (accessing property by key)
1136
+ person("foo"); // => 1
1137
+ person("name"); // => "John"
1138
+
1139
+ // String as function (accessing object property)
1140
+ "foo"(person); // => 1 (same as person("foo"))
1141
+ "bar"(person); // => 2 (same as person("bar"))
1142
+ ```
1143
+
1144
+ #### Powerful Higher-Order Function Applications
1145
+
1146
+ This feature makes higher-order functions incredibly flexible. You can pass data directly as accessor functions:
1147
+
1148
+ ```lits
1149
+ let data = [
1150
+ { name: "Alice", score: 95 },
1151
+ { name: "Bob", score: 87 },
1152
+ { name: "Carol", score: 92 }
1153
+ ];
1154
+
1155
+ // Extract names using string as function
1156
+ data map "name";
1157
+ // => ["Alice", "Bob", "Carol"]
1158
+
1159
+ // Extract scores using string as function
1160
+ data map "score";
1161
+ // => [95, 87, 92]
1162
+
1163
+ // Get second element of multiple arrays using number as function
1164
+ let arrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
1165
+ arrays map 1;
1166
+ // => [2, 5, 8]
1167
+
1168
+ // Access nested data
1169
+ let records = [
1170
+ { values: [10, 20, 30] },
1171
+ { values: [40, 50, 60] },
1172
+ { values: [70, 80, 90] }
1173
+ ];
1174
+
1175
+ // Get first value from each record's values array
1176
+ records map "values" map 0;
1177
+ // => [10, 40, 70]
1178
+ ```
1179
+
1180
+ #### Practical Examples
1181
+
1182
+ ```lits
1183
+ // Matrix column extraction
1184
+ let matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
1185
+ matrix map 1; // => [2, 5, 8] (second column)
1186
+
1187
+ // Object property extraction
1188
+ let users = [
1189
+ { id: 1, active: true },
1190
+ { id: 2, active: false },
1191
+ { id: 3, active: true }
1192
+ ];
1193
+ users map "active"; // => [true, false, true]
1194
+
1195
+ // String character extraction
1196
+ let words = ["hello", "world", "test"];
1197
+ words map 0; // => ["h", "w", "t"] (first characters)
1198
+
1199
+ // Complex data navigation
1200
+ let sales = [
1201
+ { quarter: "Q1", regions: { north: 100, south: 200 } },
1202
+ { quarter: "Q2", regions: { north: 150, south: 180 } }
1203
+ ];
1204
+ sales map "regions" map "north"; // => [100, 150]
1205
+ ```
1206
+
1207
+ This feature eliminates the need for verbose accessor functions and makes data transformation pipelines more concise and readable.
1208
+
1209
+ ### Parameter Order
1210
+
1211
+ Lits favors subject-first parameter order for better operator chaining:
1212
+
1213
+ ```lits
1214
+ // Function style
1215
+ filter([1, 2, 3, 4], odd?); // => [1, 3]
1216
+
1217
+ // Operator style (more readable)
1218
+ [1, 2, 3, 4] filter odd?; // => [1, 3]
1219
+ ```
1220
+
1221
+ ### Pipe Operator
1222
+
1223
+ The pipe operator `|>` passes the result of the left expression as the first argument to the right function:
1224
+
1225
+ ```lits
1226
+ // Without pipe operator
1227
+ reduce(map(filter([1, 2, 3, 4, 5, 6], odd?), -> $ * $), +, 0);
1228
+
1229
+ // With pipe operator (much more readable)
1230
+ [1, 2, 3, 4, 5, 6]
1231
+ |> filter(_, odd?)
1232
+ |> map(_, -> $ * $)
1233
+ |> reduce(_, +, 0);
1234
+ // => 35
1235
+
1236
+ // Simple transformations
1237
+ "hello world"
1238
+ |> upper-case
1239
+ |> split(_, " ")
1240
+ |> reverse
1241
+ |> join(_, "-");
1242
+ // => "WORLD-HELLO"
1243
+
1244
+ // Mathematical operations
1245
+ 10
1246
+ |> +(_, 5)
1247
+ |> *(_, 2)
1248
+ |> /(_, 3);
1249
+ // => 10 (10 + 5 = 15, 15 * 2 = 30, 30 / 3 = 10)
1250
+
1251
+ // Data processing pipeline
1252
+ { numbers: [1, 2, 3, 4, 5], multiplier: 3 }
1253
+ |> get(_, "numbers")
1254
+ |> filter(_, even?)
1255
+ |> map(_, *(_, 3))
1256
+ |> reduce(_, +, 0);
1257
+ // => 18 (even numbers [2, 4] -> [6, 12] -> sum = 18)
1258
+ ```
1259
+
1260
+ ### Operator Precedence
1261
+
1262
+ Lits follows a specific operator precedence order that determines how expressions are evaluated. Operators with higher precedence are evaluated first. When operators have the same precedence, they are evaluated left-to-right.
1263
+
1264
+ Here's the complete precedence table, from highest to lowest:
1265
+
1266
+ | Precedence | Operator(s) | Description | Example |
1267
+ |------------|-------------|-------------|---------|
1268
+ | 12 | `^` | Exponentiation | `2 ^ 3 ^ 2` → `2 ^ (3 ^ 2)` → `512` |
1269
+ | 11 | `*` `/` `%` | Multiplication, Division, Remainder | `6 + 4 * 2` → `6 + 8` → `14` |
1270
+ | 10 | `+` `-` | Addition, Subtraction | `10 - 3 + 2` → `7 + 2` → `9` |
1271
+ | 9 | `<<` `>>` `>>>` | Bit shift operations | `8 >> 1 + 1` → `8 >> 2` → `2` |
1272
+ | 8 | `++` | String concatenation | `"a" ++ "b" ++ "c"` → `"abc"` |
1273
+ | 7 | `<` `<=` `≤` `>` `>=` `≥` | Comparison operators | `3 + 2 > 4` → `5 > 4` → `true` |
1274
+ | 6 | `==` `!=` `≠` | Equality operators | `2 * 3 == 6` → `6 == 6` → `true` |
1275
+ | 5 | `&` `xor` `\|` | Bitwise operations | `4 \| 2 & 1` → `4 \| 0` → `4` |
1276
+ | 4 | `&&` `\|\|` `??` | Logical operations | `true && false \|\| true` → `false \|\| true` → `true` |
1277
+ | 3 | *function operators* | Binary functions used as operators | `5 max 3 + 2` → `5 max 5` → `5` |
1278
+ | 2 | `\|>` | Pipe operator | `[1,2] \|> map(_, inc) \|> sum` |
1279
+ | 1 | `?` `:` | Conditional (ternary) operator | `true ? 1 + 2 : 3` → `true ? 3 : 3` → `3` |
1280
+
1281
+ #### Examples of Precedence in Action
1282
+
1283
+ ```lits
1284
+ // Exponentiation has highest precedence
1285
+ 2 + 3 ^ 2; // => 2 + 9 = 11 (not 5^2 = 25)
1286
+
1287
+ // Multiplication before addition
1288
+ 2 + 3 * 4; // => 2 + 12 = 14 (not 5*4 = 20)
1289
+
1290
+ // String concatenation before comparison
1291
+ "a" ++ "b" == "ab"; // => "ab" == "ab" = true
1292
+
1293
+ // Comparison before logical AND
1294
+ 3 > 2 && 1 < 2; // => true && true = true
1295
+
1296
+ // Pipe has very low precedence
1297
+ [1, 2, 3] |> map(_, inc) |> vec:sum; // Evaluates left to right
1298
+
1299
+ // Conditional has lowest precedence
1300
+ true ? 2 + 3 : 4 + 5; // => true ? 5 : 9 = 5
1301
+ ```
1302
+
1303
+ #### Using Parentheses
1304
+
1305
+ When in doubt, or to make your intent clear, use parentheses to override precedence:
1306
+
1307
+ ```lits
1308
+ // Without parentheses (follows precedence)
1309
+ 2 + 3 * 4; // => 14
1310
+
1311
+ // With parentheses (explicit grouping)
1312
+ (2 + 3) * 4; // => 20
1313
+
1314
+ // Complex expression with explicit grouping
1315
+ let a = 2;
1316
+ let b = 3;
1317
+ let c = 4;
1318
+ let d = true;
1319
+ let e = false;
1320
+ let f = 10;
1321
+ let g = 5;
1322
+ ((a + b) * c) > (d && e ? f : g) // => (5 * 4) > (false ? 10 : 5) = 20 > 5 = true;
1323
+ ```
1324
+
1325
+ #### Associativity
1326
+
1327
+ Most operators are left-associative, meaning they evaluate from left to right when they have the same precedence:
1328
+
1329
+ ```lits
1330
+ 10 - 5 - 2; // => (10 - 5) - 2 = 3 (not 10 - (5 - 2) = 7)
1331
+ "a" ++ "b" ++ "c"; // => ("a" ++ "b") ++ "c" = "abc"
1332
+ ```
1333
+
1334
+ **Exception**: Exponentiation (`^`) is right-associative:
1335
+ ```lits
1336
+ 2 ^ 3 ^ 2 // => 2 ^ (3 ^ 2) = 2 ^ 9 = 512 (not (2 ^ 3) ^ 2 = 64)
680
1337
  ```
681
1338
 
682
1339
  ## Built-in Functions
@@ -695,13 +1352,34 @@ Lits comes with a comprehensive standard library of functions for:
695
1352
 
696
1353
  For a complete reference of all available functions with examples, visit the [Lits Playground](https://mojir.github.io/lits/) where you can explore the interactive documentation and try functions in real-time.
697
1354
 
1355
+ ## Serialization
1356
+
1357
+ A unique feature of Lits is that every result from evaluation is fully serializable as JSON, including functions and regular expressions:
1358
+
1359
+ ```lits
1360
+ // Functions are serializable
1361
+ let myFunction = x -> x * 2;
1362
+
1363
+ // Regular expressions are serializable
1364
+ let myRegex = #"[a-z]+";
1365
+
1366
+ // Complex data structures with functions are serializable
1367
+ let config = {
1368
+ transform: x -> x * 3,
1369
+ pattern: #"\d+",
1370
+ data: [1, 2, 3]
1371
+ };
1372
+
1373
+ // All of these can be serialized to JSON and later deserialized
1374
+ // back into working Lits values, preserving their functionality
1375
+ ```
1376
+
698
1377
  ## Modules and Exports
699
1378
 
700
1379
  ```lits
701
1380
  // Export variables and functions
702
1381
  export let pi = 3.14159;
703
1382
  export let square = x -> x * x;
704
-
705
1383
  // Exported values become available to other modules
706
1384
  ```
707
1385
 
@@ -712,7 +1390,7 @@ export let square = x -> x * x;
712
1390
  ```lits
713
1391
  let factorial = n -> n <= 1 ? 1 : n * self(n - 1);
714
1392
 
715
- factorial(5) // => 120
1393
+ factorial(5); // => 120
716
1394
  ```
717
1395
 
718
1396
  ### Array Processing
@@ -741,7 +1419,7 @@ let text = "Hello, World! How are you today?";
741
1419
  // Word count
742
1420
  let wordCount = text
743
1421
  |> split(_, #"\s+")
744
- |> count(_);
1422
+ |> count;
745
1423
  // => 6
746
1424
 
747
1425
  // Uppercase words longer than 4 characters