haskell_match 0.1.0

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +21 -0
  5. data/README.md +1484 -0
  6. data/ext/haskell_match/Cargo.lock +33 -0
  7. data/ext/haskell_match/Cargo.toml +22 -0
  8. data/ext/haskell_match/extconf.rb +41 -0
  9. data/ext/haskell_match/src/core/ast.rs +190 -0
  10. data/ext/haskell_match/src/core/error.rs +52 -0
  11. data/ext/haskell_match/src/core/exhaust.rs +699 -0
  12. data/ext/haskell_match/src/core/hs/ast.rs +256 -0
  13. data/ext/haskell_match/src/core/hs/json.rs +225 -0
  14. data/ext/haskell_match/src/core/hs/layout.rs +346 -0
  15. data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
  16. data/ext/haskell_match/src/core/hs/mod.rs +14 -0
  17. data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
  18. data/ext/haskell_match/src/core/lexer.rs +590 -0
  19. data/ext/haskell_match/src/core/mod.rs +19 -0
  20. data/ext/haskell_match/src/core/parser.rs +1116 -0
  21. data/ext/haskell_match/src/core/pretty.rs +373 -0
  22. data/ext/haskell_match/src/core/resolve.rs +336 -0
  23. data/ext/haskell_match/src/core/tree.rs +921 -0
  24. data/ext/haskell_match/src/core/typecheck.rs +226 -0
  25. data/ext/haskell_match/src/core/types.rs +404 -0
  26. data/ext/haskell_match/src/lib.rs +19 -0
  27. data/ext/haskell_match/src/ruby/mod.rs +1195 -0
  28. data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
  29. data/lib/haskell_match/binding_plan.rb +84 -0
  30. data/lib/haskell_match/case_of.rb +71 -0
  31. data/lib/haskell_match/clauses.rb +354 -0
  32. data/lib/haskell_match/data.rb +417 -0
  33. data/lib/haskell_match/deep_call.rb +98 -0
  34. data/lib/haskell_match/deriving.rb +130 -0
  35. data/lib/haskell_match/dsl.rb +71 -0
  36. data/lib/haskell_match/errors.rb +85 -0
  37. data/lib/haskell_match/field_types.rb +140 -0
  38. data/lib/haskell_match/function.rb +240 -0
  39. data/lib/haskell_match/haskell/compiler.rb +961 -0
  40. data/lib/haskell_match/haskell.rb +326 -0
  41. data/lib/haskell_match/inspect.rb +45 -0
  42. data/lib/haskell_match/lazy_list.rb +210 -0
  43. data/lib/haskell_match/native_loader.rb +64 -0
  44. data/lib/haskell_match/pattern.rb +75 -0
  45. data/lib/haskell_match/pattern_ast.rb +394 -0
  46. data/lib/haskell_match/prelude.rb +448 -0
  47. data/lib/haskell_match/scope.rb +44 -0
  48. data/lib/haskell_match/version.rb +5 -0
  49. data/lib/haskell_match.rb +41 -0
  50. metadata +124 -0
data/README.md ADDED
@@ -0,0 +1,1484 @@
1
+ # haskell_match
2
+
3
+ Haskell's pattern matching, brought to Ruby in full: algebraic data types,
4
+ clauses that destructure their arguments, and a compiler that refuses to build
5
+ a function with a hole in it. The matcher is written in Rust (via
6
+ [Rutie](https://github.com/danielpclark/rutie)), compiles each function once
7
+ into a decision tree, and runs it directly over Ruby values.
8
+
9
+ ```ruby
10
+ require "haskell_match"
11
+
12
+ HaskellMatch.data "Shape = Circle Double | Rect Double Double | Triangle Double Double Double"
13
+ include Shape
14
+
15
+ area = HaskellMatch.fn(:area) do
16
+ on("Circle r") { |r| 3.14159 * r * r }
17
+ on("Rect w h") { |w, h| w * h }
18
+ on(Triangle(a, b, c)) do |a, b, c|
19
+ s = (a + b + c) / 2.0
20
+ Math.sqrt(s * (s - a) * (s - b) * (s - c))
21
+ end
22
+ end
23
+
24
+ area.(Rect.new(2, 3)) # => 6
25
+ area.(Triangle[3, 4, 5]) # => 6.0
26
+ ```
27
+
28
+ Two of those clauses are Haskell written in a string; the third is the same
29
+ pattern written as Ruby. Both forms build the same decision tree, and you can
30
+ use whichever reads better, line by line.
31
+
32
+ ## Why every path must be accounted for
33
+
34
+ In most Ruby code, the case you forgot is found by a user. A `case` with no
35
+ `else` silently returns `nil`; an `if` chain that misses a branch falls
36
+ through; a `Hash#fetch` without a default raises in production at three in the
37
+ morning. The fix is always the same, and always late: add the branch you did
38
+ not think of.
39
+
40
+ Haskell inverts this. A function defined by patterns is a *claim* about the
41
+ shape of its input, and the compiler checks the claim: if the clauses do not
42
+ cover every value the type allows, the program does not compile, and the
43
+ message tells you exactly which values fell through. haskell_match brings that
44
+ discipline to Ruby at definition time:
45
+
46
+ ```ruby
47
+ HaskellMatch.fn(:area) do
48
+ on("Circle r") { |r| 3.14159 * r * r }
49
+ on("Rect w h") { |w, h| w * h }
50
+ end
51
+ # HaskellMatch::NonExhaustiveError:
52
+ # Pattern match(es) are non-exhaustive
53
+ # In an equation for 'area':
54
+ # Patterns not matched:
55
+ # Triangle _ _ _
56
+ ```
57
+
58
+ This changes how code evolves. Add a constructor to a type (`| Hexagon
59
+ Double`) and every function that matches on that type fails to load, each one
60
+ pointing at the exact case to write. There is no grep for call sites, no
61
+ "should be fine", no test suite hoping to cover the new branch: the compiler
62
+ has already enumerated the paths and found the missing one. Teams that adopt
63
+ this stop writing defensive `else raise "unreachable"` branches, because
64
+ unreachable is now something the compiler proves rather than something a
65
+ comment asserts.
66
+
67
+ The same analysis catches the opposite mistake, a clause that can never run:
68
+
69
+ ```ruby
70
+ HaskellMatch.data "Maybe a = Nothing | Just a"
71
+ include Maybe
72
+
73
+ HaskellMatch.fn(:describe) do
74
+ on("_") { "something" }
75
+ on("Just x") { |x| "just #{x}" }
76
+ end
77
+ # HaskellMatch::RedundantClauseError:
78
+ # Pattern match is redundant
79
+ # In an equation for 'describe':
80
+ # describe Just x = ... (example.rb:3)
81
+ ```
82
+
83
+ Redundant clauses are dead code with a story: usually an earlier clause grew
84
+ broader than intended, or two people each handled a case. Either way the
85
+ compiler found the contradiction in the reasoning before it became a bug.
86
+
87
+ The check is precise, not merely cautious. Nested patterns, literals, lists,
88
+ tuples and records are all enumerated, and the witnesses are concrete:
89
+
90
+ ```ruby
91
+ HaskellMatch.data "Tree a = Leaf | Node (Tree a) a (Tree a)"
92
+ include Tree
93
+
94
+ HaskellMatch.fn(:depth) do
95
+ on("Leaf") { 0 }
96
+ on("Node Leaf _ Leaf") { 1 }
97
+ on("Node (Node _ _ _) _ _") { |*| :deep }
98
+ end
99
+ # Patterns not matched:
100
+ # Node Leaf _ (Node _ _ _)
101
+ ```
102
+
103
+ Haskell's type checker also rejects a function that matches a `Maybe` in one
104
+ clause and a list in another. Ruby has no static types, so haskell_match
105
+ checks what it can at definition time (all clauses must agree on the shape of
106
+ each position) and defers the rest to the call: a value that *no* clause could
107
+ accept raises `TypeMismatchError`, while `_` and variables accept anything,
108
+ exactly as Haskell's wildcard does.
109
+
110
+ ## A tour, in both dialects
111
+
112
+ Everything below mixes the two ways of writing a pattern. A String given to
113
+ `on` is Haskell syntax; anything else is the pattern written in place, where
114
+ bare names are variables, `_` is the wildcard, `[x, *xs]` is `(x:xs)`, and
115
+ `Just(x)` or `Just[x]` applies a constructor.
116
+
117
+ ### Data types and constructors
118
+
119
+ ```ruby
120
+ HaskellMatch.data "Either a b = Left a | Right b"
121
+ HaskellMatch.data "Contact = Person { name :: String, age :: Int }"
122
+ include Either
123
+ include Contact
124
+
125
+ Just.new(1) # => Just 1
126
+ Just[Just[Nothing]] # => Just (Just Nothing)
127
+ Person.new(name: "Ann", age: 30) # => Person {name = "Ann", age = 30}
128
+ [1, 2].map(&Just) # => [Just 1, Just 2]
129
+ Nothing.frozen? # => true
130
+ ```
131
+
132
+ Constructors are frozen `Data` values: they compare by value, hash, print as
133
+ Haskell would, and work with Ruby's own `case/in` too.
134
+
135
+ ### Maybe and Either, the everyday cases
136
+
137
+ ```ruby
138
+ from_maybe = HaskellMatch.fn(:from_maybe) do
139
+ on("d", "Nothing") { |d| d }
140
+ on(_, Just(x)) { |x| x }
141
+ end
142
+ from_maybe.(0, Just.new(5)) # => 5
143
+ from_maybe.(0, Nothing) # => 0
144
+
145
+ either = HaskellMatch.fn(:either) do
146
+ on("Left e") { |e| "error: #{e}" }
147
+ on(Right(v)) { |v| "ok: #{v}" }
148
+ end
149
+ either.(Left.new("boom")) # => "error: boom"
150
+ either.(Right[42]) # => "ok: 42"
151
+ ```
152
+
153
+ ### Lists, strings and recursion
154
+
155
+ A Ruby Array is a Haskell list, and so is a Ruby String (`String = [Char]`):
156
+
157
+ ```ruby
158
+ length = HaskellMatch.fn(:length) do
159
+ on("[]") { 0 }
160
+ on([_, *xs]) { |xs| 1 + length.(xs) }
161
+ end
162
+ length.([1, 2, 3]) # => 3
163
+ length.("haskell") # => 7
164
+
165
+ zip = HaskellMatch.fn(:zip) do
166
+ on("(x:xs)", [y, *ys]) { |x, xs, y, ys| [[x, y]] + zip.(xs, ys) }
167
+ on("_", "_") { [] }
168
+ end
169
+ zip.([1, 2, 3], %w[a b]) # => [[1, "a"], [2, "b"]]
170
+
171
+ greeting = HaskellMatch.fn(:greeting) do
172
+ on('""') { "Hello, stranger" }
173
+ on("('A':_)") { "Hello, A-person" }
174
+ on([c, *_]) { |c| "Hello, #{c.upcase}-person" }
175
+ end
176
+ greeting.("") # => "Hello, stranger"
177
+ greeting.("Ann") # => "Hello, A-person"
178
+ greeting.("bob") # => "Hello, B-person"
179
+ ```
180
+
181
+ Bound tails share storage with the original (a copy-on-write slice), so
182
+ recursing down a list does not copy it.
183
+
184
+ ### Guards, as-patterns and nesting
185
+
186
+ ```ruby
187
+ classify = HaskellMatch.fn(:classify) do
188
+ on(Just(x), guard: ->(x) { x.negative? }) { :negative }
189
+ on("Just 0") { :zero }
190
+ on(Just(x), where: ->(x) { x.even? }) { :even }
191
+ on("Just _") { :odd }
192
+ on(Nothing) { :none }
193
+ end
194
+ [Just[-1], Just[0], Just[2], Just[3], Nothing].map(&classify)
195
+ # => [:negative, :zero, :even, :odd, :none]
196
+
197
+ dedupe = HaskellMatch.fn(:dedupe) do
198
+ on("(x:rest@(y:_))", guard: ->(x, y) { x == y }) { |rest| dedupe.(rest) }
199
+ on([x, *rest]) { |x, rest| [x] + dedupe.(rest) }
200
+ on([]) { [] }
201
+ end
202
+ dedupe.([1, 1, 2, 3, 3, 3, 4]) # => [1, 2, 3, 4]
203
+
204
+ flatten_maybe = HaskellMatch.fn(:flatten_maybe) do
205
+ on("Just (Just x)") { |x| Just[x] }
206
+ on(Just(Nothing)) { Nothing }
207
+ on(Nothing) { Nothing }
208
+ end
209
+ flatten_maybe.(Just[Just[7]]) # => Just 7
210
+ ```
211
+
212
+ A guarded clause may fall through, so (as in GHC) it does not count towards
213
+ coverage; `otherwise` does.
214
+
215
+ ### Records and tuples
216
+
217
+ ```ruby
218
+ can_vote = HaskellMatch.fn(:can_vote) do
219
+ on("Person { age = a }", guard: ->(a) { a >= 18 }) { true }
220
+ on(Person(**_)) { false }
221
+ end
222
+ can_vote.(Person.new("Ann", 30)) # => true
223
+
224
+ introduce = HaskellMatch.fn(:introduce) do
225
+ on(Person(name: n, age: a)) { |n, a| "#{n} is #{a}" }
226
+ end
227
+ introduce.(Person.new("Bob", 7)) # => "Bob is 7"
228
+
229
+ swap = HaskellMatch.fn(:swap) { on("(a, b)") { |a, b| [b, a] } }
230
+ swap.([1, 2]) # => [2, 1]
231
+
232
+ dist = HaskellMatch.fn(:dist) { on(tuple(x1, y1), tuple(x2, y2)) { |x1, y1, x2, y2| Math.hypot(x2 - x1, y2 - y1) } }
233
+ dist.([0, 0], [3, 4]) # => 5.0
234
+ ```
235
+
236
+ Tuples are Arrays of a fixed length, lists are Arrays of any length; the
237
+ compiler keeps the two apart just as Haskell does.
238
+
239
+ ### Expressions, patterns as objects, and methods
240
+
241
+ ```ruby
242
+ # case ... of
243
+ HaskellMatch.case_of(Just[3]) do
244
+ on("Just x", guard: ->(x) { x > 10 }) { |x| "big #{x}" }
245
+ on(Just(x)) { |x| "just #{x}" }
246
+ on(Nothing) { "nothing" }
247
+ end # => "just 3"
248
+
249
+ # a pattern on its own, usable in case/when
250
+ head = HaskellMatch.pattern { Just([x, *_]) }
251
+ head.match(Just[[9, 8]]) # => {:x=>9}
252
+ head === Just[[]] # => false
253
+
254
+ # methods defined by clauses, with access to self
255
+ class Account
256
+ extend HaskellMatch::DSL
257
+ include Maybe
258
+
259
+ attr_reader :balance
260
+ def initialize(balance) = @balance = balance
261
+ def limit = 100
262
+
263
+ hdef :deposit do
264
+ on(Nothing) { self }
265
+ on(Just(amt), guard: ->(amt) { amt <= limit }) { |amt| Account.new(balance + amt) }
266
+ on("Just amt") { |amt| raise ArgumentError, "over limit: #{amt}" }
267
+ end
268
+ end
269
+ Account.new(10).deposit(Just[50]).balance # => 60
270
+ ```
271
+
272
+ ### Bindings your way
273
+
274
+ Name your block parameters after the pattern's variables and take any subset
275
+ in any order; keywords work too:
276
+
277
+ ```ruby
278
+ f = HaskellMatch.fn(:f) do
279
+ on("(x:xs)") { |xs| xs } # by name
280
+ on("[]") { [] }
281
+ end
282
+ f.([1, 2, 3]) # => [2, 3]
283
+
284
+ g = HaskellMatch.fn(:g) { on([x, *xs]) { |x:, xs:| { x: x, xs: xs } }; on([]) { {} } }
285
+ g.([1, 2]) # => {:x=>1, :xs=>[2]}
286
+ ```
287
+
288
+ ## Recursion without limits, and laziness
289
+
290
+ Haskell programs loop by recursing and process infinite data by being lazy.
291
+ Ruby's VM gives a fixed stack to each thread and evaluates eagerly, so a
292
+ faithful port has to supply both. haskell_match does, in three complementary
293
+ ways.
294
+
295
+ ### Plain recursion goes as deep as memory allows
296
+
297
+ ```ruby
298
+ count = HaskellMatch.fn(:count) do
299
+ on([]) { 0 }
300
+ on([_, *xs]) { |xs| 1 + count.(xs) }
301
+ end
302
+ count.((1..200_000).to_a) # => 200000
303
+ ```
304
+
305
+ A Ruby lambda written the same way dies with `SystemStackError` around 10,000
306
+ levels. Here the native call runs every hundredth nested body on a fresh
307
+ Fiber, chaining their stacks the way GHC grows its own, so the depth limit
308
+ becomes memory rather than a fixed buffer. You write the obvious code and it
309
+ works; the cost is about 1.6 KB per level while the recursion is pending, and
310
+ `HaskellMatch.max_depth` (250,000 by default) turns a runaway recursion into
311
+ a clear `StackOverflowError` instead of a swallowed machine. A function that
312
+ is *meant* to recurse deep can be defined with `deep: true`: its body is then
313
+ invoked from Ruby rather than from the native matcher, which costs about one
314
+ extra Ruby frame per call but brings a level down to about 100 bytes and
315
+ makes garbage collection at depth six times cheaper.
316
+
317
+ ### Tail calls run in constant space
318
+
319
+ ```ruby
320
+ sum = HaskellMatch.fn(:sum) do
321
+ on(acc, []) { |acc| acc }
322
+ on(acc, [x, *xs]) { |acc, x, xs| sum.tail(acc + x, xs) }
323
+ end
324
+ sum.(0, (1..1_000_000).to_a) # => 500000500000
325
+
326
+ collatz_steps = HaskellMatch.fn(:collatz_steps) do
327
+ on(1, n) { |n| n }
328
+ on(k, n, guard: ->(k) { k.even? }) { |k, n| collatz_steps.tail(k / 2, n + 1) }
329
+ on(k, n) { |k, n| collatz_steps.tail(3 * k + 1, n + 1) }
330
+ end
331
+ collatz_steps.(27, 0) # => 111
332
+ ```
333
+
334
+ `f.tail(args)` is Haskell's tail call made explicit: the native loop replaces
335
+ the arguments and matches again on the same frame. Any loop a Haskell program
336
+ would write as an accumulator, a fold, a state machine or a server loop runs
337
+ this way in O(1) space, including mutual recursion between functions, with
338
+ nothing counted against the depth limit.
339
+
340
+ ### Deferred calls keep deep recursion cheap
341
+
342
+ ```ruby
343
+ length = HaskellMatch.fn(:length) do
344
+ on([]) { 0 }
345
+ on([_, *xs]) { |xs| length.defer(xs) { |n| 1 + n } } # 1 + length xs
346
+ end
347
+ length.((1..2_000_000).to_a) # => 2000000
348
+ ```
349
+
350
+ `defer` names the continuation that plain recursion leaves implicit. The
351
+ pending blocks live on a heap stack owned by the native call, about 200 bytes
352
+ each, chunked so Ruby's generational GC never rescans the whole stack. Use it
353
+ when a non-tail recursion is known to go very deep and memory matters.
354
+
355
+ ### Lazy lists make infinite data ordinary
356
+
357
+ ```ruby
358
+ take = HaskellMatch.fn(:take) do
359
+ on(0, _) { [] }
360
+ on(_, []) { [] }
361
+ on(n, [x, *xs]) { |n, x, xs| [x] + take.(n - 1, xs) }
362
+ end
363
+
364
+ naturals = HaskellMatch.lazy(1..)
365
+ take.(5, naturals) # => [1, 2, 3, 4, 5]
366
+
367
+ powers = HaskellMatch::LazyList.iterate(1) { |x| x * 2 }
368
+ take.(8, powers) # => [1, 2, 4, 8, 16, 32, 64, 128]
369
+
370
+ fibs = HaskellMatch.lazy(Enumerator.new { |y| a, b = 0, 1; loop { y << a; a, b = b, a + b } })
371
+ take.(10, fibs) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
372
+
373
+ # any Enumerator is a list; Ruby's lazy pipelines compose with the patterns
374
+ take.(3, (1..).lazy.select(&:even?).map { |x| x * x }) # => [4, 16, 36]
375
+ ```
376
+
377
+ A `LazyList` is a memoised cons list: `(x:xs)` forces one cell, binds `xs` to
378
+ the rest still unevaluated, and every forced cell is computed once and shared
379
+ by all consumers, which is exactly Haskell's evaluation model for lists. The
380
+ advantages carry over intact. Producers and consumers are written separately
381
+ and composed; a generator never needs to know how much of it will be used;
382
+ `take.(n, expensive_stream)` does `n` units of work and no more; and the same
383
+ `take` serves finite Arrays, Strings, lazy lists and Ruby Enumerators without
384
+ a line changing, because they are all one type to the pattern compiler.
385
+
386
+ Together these give Ruby the two things Haskell relies on for "infinite"
387
+ programs: loops that do not consume stack, and data that does not have to
388
+ exist before it is asked for.
389
+
390
+ ## Or simply write Haskell
391
+
392
+ Everything above is Haskell's pattern matching with Ruby expressions in the
393
+ clause bodies. When a function is clearer in Haskell itself, write it in
394
+ Haskell. `HaskellMatch.haskell` compiles a Haskell 2010 subset (data
395
+ declarations, equations, guards, `where`, `let`, `case`, lambdas, sections,
396
+ list comprehensions, ranges, and a lazy Prelude) into methods on a Ruby
397
+ module, with the same exhaustiveness and redundancy checks as everything
398
+ else in this library:
399
+
400
+ ```ruby
401
+ Shapes = HaskellMatch.haskell(<<~HS)
402
+ data Shape = Circle Double | Rect Double Double
403
+
404
+ area :: Shape -> Double
405
+ area (Circle r) = 3 * r * r
406
+ area (Rect w h) = w * h
407
+
408
+ describe :: Shape -> String
409
+ describe s
410
+ | a > 10 = "big " ++ kind
411
+ | otherwise = "small " ++ kind
412
+ where
413
+ a = area s
414
+ kind = case s of
415
+ Circle _ -> "circle"
416
+ Rect w h | w == h -> "square"
417
+ | otherwise -> "rectangle"
418
+
419
+ totalArea :: [Shape] -> Double
420
+ totalArea = sum . map area
421
+ HS
422
+
423
+ shapes = [Shapes::Circle.new(1.0), Shapes::Rect.new(2.0, 2.0), Shapes::Rect.new(3.0, 5.0)]
424
+ shapes.map { |s| Shapes.describe(s) } # => ["small circle", "small square", "big rectangle"]
425
+ Shapes.total_area(shapes) # => 22.0
426
+ ```
427
+
428
+ Haskell names arrive as Ruby methods (`totalArea` is also `total_area`),
429
+ constructors as constants, and the recursion and laziness machinery is the
430
+ one described above: `where` helpers that call themselves in tail position
431
+ run in constant space, and a list built with `:` is as lazy as Haskell's, so
432
+ the classic infinite definitions work unchanged.
433
+
434
+ ```ruby
435
+ Nums = HaskellMatch.haskell(<<~HS)
436
+ primes :: [Int]
437
+ primes = sieve [2..]
438
+ where
439
+ sieve [] = []
440
+ sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
441
+
442
+ fibs :: [Integer]
443
+ fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
444
+
445
+ sumTo :: Int -> Int
446
+ sumTo n = go n 0
447
+ where
448
+ go 0 acc = acc
449
+ go k acc = go (k - 1) (acc + k)
450
+ HS
451
+
452
+ Nums.primes.take(8) # => [2, 3, 5, 7, 11, 13, 17, 19]
453
+ Nums.fibs.take(10) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
454
+ Nums.sum_to(1_000_000) # => 500000500000
455
+ ```
456
+
457
+ The two languages call each other freely. A name the Haskell does not define
458
+ is a Ruby method of the host module, so Haskell can lean on Ruby for
459
+ formatting, I/O or anything else; and Ruby can match on the module's types
460
+ with the module's own `fn`, `case_of` and `pattern`, in either pattern
461
+ dialect:
462
+
463
+ ```ruby
464
+ module Report
465
+ def self.money(x)
466
+ format("$%.2f", x)
467
+ end
468
+
469
+ extend HaskellMatch::Haskell
470
+ haskell <<~HS
471
+ data Line = Line String Double Int
472
+
473
+ total :: [Line] -> Double
474
+ total ls = sum [price * fromIntegral qty | Line _ price qty <- ls]
475
+
476
+ summary :: [Line] -> String
477
+ summary [] = "nothing ordered"
478
+ summary ls = show (length ls) ++ " lines, " ++ money (total ls)
479
+ HS
480
+ end
481
+
482
+ order = [Report::Line.new("tea", 2.5, 2), Report::Line.new("cake", 4.0, 1)]
483
+ Report.summary(order) # => "2 lines, $9.00"
484
+ Report.summary([]) # => "nothing ordered"
485
+
486
+ label = Report.fn(:label) do
487
+ on(Line(name, _, 1)) { |name| name }
488
+ on("Line name _ qty") { |name, qty| "#{qty} x #{name}" }
489
+ end
490
+ order.map { |l| label.(l) } # => ["2 x tea", "cake"]
491
+ ```
492
+
493
+ And a Haskell file is just a Haskell file. `HaskellMatch.require` finds
494
+ `name.hs` on the load path (or takes a path) and defines a constant named
495
+ after its `module` header; `HaskellMatch.load` returns an anonymous module.
496
+ No templating and no interpolation: the file is the Haskell 2010 subset
497
+ described in [Inline Haskell and `.hs` files](#inline-haskell-and-hs-files).
498
+
499
+ ```ruby
500
+ HaskellMatch.require "examples/geometry" # examples/geometry.hs: `module Geometry where ...`
501
+ Geometry.describe(Geometry::Triangle.new(3.0, 4.0, 5.0)) # => "small triangle"
502
+ ```
503
+
504
+ ## At a glance
505
+
506
+ * **Haskell pattern syntax**, quoted or written in place: constructors,
507
+ literals, variables, wildcards, lists, tuples, as-patterns `all@(x:_)`,
508
+ lazy patterns `~p`, bang patterns `!x`, record patterns `Person { name = n, .. }`,
509
+ guards and `otherwise`, with both forms freely mixed.
510
+ * **Algebraic data types** declared with Haskell `data` syntax; constructors
511
+ are frozen `Data` values with Haskell-style `inspect`.
512
+ * **Every logical path accounted for**: non-exhaustive and redundant clauses
513
+ are definition-time errors with GHC-style messages; positions whose
514
+ patterns disagree in type are a compile error; `_` and variables match
515
+ anything, and a value no pattern can accept raises `TypeMismatchError`.
516
+ * **Strings are lists of characters**, as in Haskell; lazy lists and
517
+ Enumerators are lists too.
518
+ * **Recursion without Ruby's stack limit**: chained Fiber stacks, constant-
519
+ space tail calls, deferred continuations, and a depth guard.
520
+ * **Fast**: one Maranget-style decision tree per function, matching in Rust
521
+ over Ruby `VALUE`s with no allocation until a clause is chosen, and list
522
+ tails as shared slices.
523
+ * **Thread-, fiber- and Ractor-safe**, with `ractor: true` producing
524
+ shareable functions.
525
+ * **Haskell itself**, inline or from `.hs` files: a Haskell 2010 subset
526
+ compiled to Ruby methods, with a lazy Prelude and two-way interop.
527
+
528
+ ## Installation
529
+
530
+ You need Ruby ≥ 3.2 and a Rust toolchain (`cargo`). The gem builds the
531
+ extension on install:
532
+
533
+ ```sh
534
+ gem install haskell_match # or add it to your Gemfile
535
+ ```
536
+
537
+ From a checkout:
538
+
539
+ ```sh
540
+ bundle install
541
+ bundle exec rake compile # cargo build --release, copies the library into lib/
542
+ bundle exec rake test # Ruby test suite (compiles first)
543
+ bundle exec rake cargo:test # Rust unit tests for the pure core
544
+ bundle exec rake bench
545
+ ```
546
+
547
+ The library is loaded with `Fiddle`; set `HASKELL_MATCH_NATIVE` to point at a
548
+ specific build if needed.
549
+
550
+ ## Declaring data types
551
+
552
+ ```ruby
553
+ HaskellMatch.data "Maybe a = Nothing | Just a"
554
+ HaskellMatch.data "Either a b = Left a | Right b"
555
+ HaskellMatch.data "Tree a = Leaf | Node (Tree a) a (Tree a)"
556
+ HaskellMatch.data "Person = Person { name :: String, age :: Int }"
557
+ HaskellMatch.data "Color = Red | Green | Blue deriving (Show, Eq)" # deriving is accepted and ignored
558
+
559
+ # the same, without the declaration syntax
560
+ HaskellMatch.data :Maybe, Nothing: 0, Just: 1
561
+ HaskellMatch.data :Person, Person: { name: :String, age: :Int }
562
+ ```
563
+
564
+ `HaskellMatch.data` defines a module named after the type (as a top-level
565
+ constant by default; pass `under: SomeModule`, or `under: nil` to get it back
566
+ without defining a constant). The module holds one constant per constructor,
567
+ so `include Maybe` brings `Just` and `Nothing` into scope.
568
+
569
+ * Constructors with fields are `Data` classes: `Just.new(1)`, `Just[1]`,
570
+ `Just.(1)`, `[1, 2].map(&Just)`. Positional fields are `_1`, `_2`, ...;
571
+ record fields have their own names. Values are frozen, compare by value,
572
+ hash, and print Haskell-style: `Just (Just 1)`, `Person {name = "Ann", age = 30}`.
573
+ They also work with Ruby's own `case/in` (`deconstruct_keys`).
574
+ * Nullary constructors are singleton values: `Nothing`, `Red`.
575
+ * `Maybe === value` tests membership; `Maybe.constructors` lists them.
576
+ * Types are closed: declaring `data Pet = Dog | Cat` later with a different
577
+ set of constructors replaces the type; existing compiled functions keep
578
+ working against the old values.
579
+
580
+ Haskell keeps types and constructors in different namespaces; Ruby does not.
581
+ For `data Person = Person {...}`, after `include Person` the name `Person`
582
+ refers to the constructor; the type module is `::Person` or
583
+ `Person.data_type`. The type module forwards `new`, `[]` and `call` to a
584
+ same-named constructor, so `Person.new("Al", 3)` builds a person either way.
585
+
586
+ ## Patterns
587
+
588
+ | Haskell | Matches |
589
+ |----------------------------------|------------------------------------------------------------|
590
+ | `x`, `_`, `_name` | anything (variables bind, `_` does not) |
591
+ | `Just x`, `Nothing`, `Node l v r`| a constructor and its fields |
592
+ | `Person { name = n, age }` | record fields by name (`age` is a pun); `Person { .. }` binds all |
593
+ | `[]`, `[a, b]`, `(x:xs)`, `x:y:rest` | Ruby Arrays viewed as lists |
594
+ | `(a, b)`, `(a, b, c)`, `()` | Ruby Arrays of exactly that length |
595
+ | `True`, `False` | `true`, `false` |
596
+ | `0`, `-1`, `1.5`, `0xFF`, `12345678901234567890` | numbers (`0` also matches `0.0`, as in Haskell) |
597
+ | `'c'` | a character: a one-character Ruby String, or one character of a String matched as a list |
598
+ | `"text"` | the list `['t', 'e', 'x', 't']`: `String = [Char]`, so `f "" = ...; f (c:cs) = ...` works on Ruby Strings |
599
+ | `:sym`, `:"quoted"` | Ruby Symbols (an extension) |
600
+ | `all@(x:_)` | as-pattern |
601
+ | `~(a, b)` | lazy (irrefutable) pattern: always matches; destructured only when the clause runs |
602
+ | `!x` | bang pattern (accepted; Ruby is strict anyway) |
603
+ | `Data.Maybe.Just x` | qualification is ignored |
604
+
605
+ Comments (`-- ...`, `{- ... -}`) are allowed inside patterns.
606
+
607
+ Ruby Strings are lists of characters wherever a list pattern appears: `[]`
608
+ matches `""`, `(c:cs)` binds `c` to a one-character String and `cs` to the
609
+ rest (a String), and `['y', _]` matches any two-character String starting
610
+ with `y`. The same patterns match Arrays of one-character Strings. `Char` and
611
+ `String` are different types, as in Haskell: `'a'` and `"a"` cannot appear in
612
+ the same position.
613
+
614
+ ### Wildcards and run-time types
615
+
616
+ `_` and variables match any value and never fail. Constructor and literal
617
+ patterns fail on values of another type, which simply moves matching on to
618
+ the next clause. Only when no pattern of the function can accept a value is
619
+ `HaskellMatch::TypeMismatchError` raised, the run-time counterpart of the
620
+ compile error Haskell would give:
621
+
622
+ ```ruby
623
+ f = HaskellMatch.fn(:f) { on("Just x") { |x| x }; on("_") { :other } }
624
+ f.(5) # => :other `_` accepts anything
625
+ g = HaskellMatch.fn(:g) { on("Just x") { |x| x }; on("Nothing") { 0 } }
626
+ g.(5) # TypeMismatchError: expected a value of type Maybe but got 5 (Integer)
627
+ ```
628
+
629
+ ### Patterns without quotes
630
+
631
+ Inside a definition block the pattern can be written as a Ruby expression.
632
+ Bare lower-case names are variables, `_` is the wildcard, and the expression
633
+ is rendered to the Haskell syntax above and compiled identically, so
634
+ exhaustiveness checks, errors and speed are the same. Quoted and in-place
635
+ patterns can be mixed, even within one clause.
636
+
637
+ | In place | Haskell |
638
+ |--------------------------------|-----------------------------|
639
+ | `x`, `_`, `var(:name)` | `x`, `_`, `name` (`var` for a name taken by a method or local) |
640
+ | `Just(x)`, `Just[x]`, `Nothing`| `Just x`, `Nothing` |
641
+ | `Node(Leaf, v, Node(_, _, _))` | `Node Leaf v (Node _ _ _)` |
642
+ | `Person(name: n, age: _)` | `Person { name = n, age = _ }` |
643
+ | `Person(name: n, **_)` | `Person { name = n, .. }` |
644
+ | `[]`, `[a, b]` | `[]`, `[a, b]` |
645
+ | `[x, *xs]`, `cons(x, xs)` | `(x:xs)` |
646
+ | `[x, y, *_]` | `(x:y:_)` |
647
+ | `tuple(a, b)`, `unit` | `(a, b)`, `()` |
648
+ | `as(all, [x, *_])` | `all@(x:_)` |
649
+ | `lazy(tuple(a, b))`, `bang(x)` | `~(a, b)`, `!x` |
650
+ | `0`, `-1`, `1.5`, `true`, `:ok`| `0`, `-1`, `1.5`, `True`, `:ok` |
651
+ | `str("abc")`, `char("c")` | `"abc"`, `'c'` |
652
+
653
+ A String handed directly to `on` is quoted pattern syntax (`on("Just x")`);
654
+ inside a pattern a Ruby String is a string literal (`Just("abc")`), and
655
+ `str("abc")` makes one at the top level. A bare name that is also a method
656
+ of the enclosing object (or a local variable) is not a pattern variable;
657
+ use `var(:name)` for it. `HaskellMatch.pattern { Just([x, *_]) }` builds a
658
+ standalone pattern the same way.
659
+
660
+ ### Guards
661
+
662
+ ```ruby
663
+ sign = HaskellMatch.fn(:sign) do
664
+ on("n", guard: ->(n) { n > 0 }) { 1 }
665
+ on("n", guard: ->(n) { n < 0 }) { -1 }
666
+ on("_", guard: otherwise) { 0 } # or simply on("_") { 0 }
667
+ end
668
+ ```
669
+
670
+ A guarded clause does not count towards exhaustiveness (a guard may fail),
671
+ exactly as in GHC; `otherwise` does.
672
+
673
+ ### Bindings
674
+
675
+ Bound variables are handed to the body in the order they appear in the
676
+ pattern. Name your block parameters after the variables and you may take any
677
+ subset in any order; keyword parameters work too:
678
+
679
+ ```ruby
680
+ on("(x:xs)") { |xs| ... } # by name
681
+ on("(x:xs)") { |xs, x| ... } # reordered
682
+ on("(x:xs)") { |x:, xs:| ... } # keywords
683
+ on("(x:xs)") { |**all| all } # => { x: 1, xs: [2, 3] }
684
+ on("(x:xs)") { |*vals| vals } # positional, pattern order
685
+ ```
686
+
687
+ Naming a parameter that the pattern does not bind is a `DefinitionError`.
688
+
689
+ ### Recursion
690
+
691
+ A function is in scope inside its own clauses under its name, and as `recur`:
692
+
693
+ ```ruby
694
+ fact = HaskellMatch.fn(:fact) do
695
+ on(0) { 1 }
696
+ on(n) { |n| n * fact.(n - 1) } # or recur.(n - 1)
697
+ end
698
+ ```
699
+
700
+ Ruby's VM stack is fixed in size, so plain recursion written in Ruby dies at
701
+ roughly 10,000 levels. haskell_match removes that limit the way GHC's growable
702
+ stack does, with three mechanisms:
703
+
704
+ * **Stack segments (automatic).** Every `HaskellMatch.stack_segment` nested
705
+ calls (default 100) the next clause body runs in a fresh Fiber, which brings
706
+ its own VM and machine stacks. `1 + length.(xs)` therefore recurses as deep
707
+ as memory allows with no change to the code. The cost is about 1.6 KB per
708
+ level (each segment commits a 128 KB fiber VM stack), reclaimed when the
709
+ call returns. Non-local exits (`throw`, `break`) do not cross segment
710
+ boundaries.
711
+ * **Tail calls (constant space).** Return `function.tail(args...)` from a
712
+ body and the call continues with those arguments on the same frame, like a
713
+ Haskell loop:
714
+
715
+ ```ruby
716
+ sum = HaskellMatch.fn(:sum) do
717
+ on(acc, []) { |acc| acc }
718
+ on(acc, [x, *xs]) { |acc, x, xs| sum.tail(acc + x, xs) }
719
+ end
720
+ sum.(0, (1..1_000_000).to_a) # => 500000500000
721
+ ```
722
+ * **Deferred calls (cheap depth).** `function.defer(args...) { |result| ... }`
723
+ stands for a non-tail call whose result the block receives; the pending
724
+ blocks are kept on a heap-backed stack managed natively, at about 200 bytes
725
+ per level:
726
+
727
+ ```ruby
728
+ length = HaskellMatch.fn(:length) do
729
+ on([]) { 0 }
730
+ on([_, *xs]) { |xs| length.defer(xs) { |n| 1 + n } } # 1 + length xs
731
+ end
732
+ ```
733
+
734
+ `HaskellMatch.max_depth` (default 250,000 nested calls, about 400 MB at the
735
+ segment cost) raises `HaskellMatch::StackOverflowError` beyond that depth, so
736
+ a runaway recursion fails instead of taking the machine's memory; set it
737
+ higher, or to 0 for no limit, when a computation legitimately needs more.
738
+ Tail calls do not count towards the depth. The section "Deep and infinite
739
+ recursion: expert notes" below gives the measured costs and the semantics at
740
+ segment boundaries.
741
+
742
+ ### Lazy lists
743
+
744
+ `HaskellMatch.lazy(enumerable)` builds a memoised lazy list; list patterns
745
+ match it element by element, forcing only what they inspect, so infinite
746
+ lists work as in Haskell. Ruby Enumerators (including `Enumerator::Lazy`)
747
+ given to a function are wrapped automatically, iterating from the start each
748
+ time.
749
+
750
+ ```ruby
751
+ take = HaskellMatch.fn(:take) do
752
+ on(0, _) { [] }
753
+ on(_, []) { [] }
754
+ on(n, [x, *xs]) { |n, x, xs| [x] + take.(n - 1, xs) }
755
+ end
756
+ take.(5, HaskellMatch.lazy(1..)) # => [1, 2, 3, 4, 5]
757
+ take.(4, HaskellMatch::LazyList.iterate(1) { |x| x * 2 }) # => [1, 2, 4, 8]
758
+ take.(3, (1..).lazy.map { |x| x * x }) # => [1, 4, 9]
759
+ ```
760
+
761
+ `LazyList` is Enumerable and offers `head`, `tail`, `take(n)`, `empty?`, plus
762
+ constructors `iterate`, `repeat`, `range`, `generate` and `empty`.
763
+
764
+ ### Clause bodies and `self`
765
+
766
+ The definition block runs with `self` set to the clause builder. Method calls
767
+ the builder does not understand are forwarded to the object that owns the
768
+ block, so helper methods remain callable. Instance variables are not visible;
769
+ take the builder as a parameter when you need them:
770
+
771
+ ```ruby
772
+ HaskellMatch.fn(:f) { |m| m.on("Just x") { |x| x + @offset }; m.on("Nothing") { @offset } }
773
+ ```
774
+
775
+ Methods defined with `hdef` run their bodies with `self` set to the receiver
776
+ (see below).
777
+
778
+ ## Three ways to match
779
+
780
+ ### `HaskellMatch.fn` — compiled functions
781
+
782
+ ```ruby
783
+ zip = HaskellMatch.fn(:zip) do
784
+ on("(x:xs)", "(y:ys)") { |x, xs, y, ys| [[x, y]] + zip.(xs, ys) }
785
+ on("_", "_") { [] }
786
+ end
787
+ zip.([1, 2, 3], %i[a b]) # => [[1, :a], [2, :b]]
788
+ zip.arity # => 2
789
+ zip.bindings # => [["x", "xs", "y", "ys"], []]
790
+ zip.to_proc, zip.curry, zip[a, b], zip === a # Proc-like protocol
791
+ zip.select(args...) # => [clause_index, [bound values]] or nil
792
+ zip.decision_tree # dump of the compiled tree
793
+ ```
794
+
795
+ Options: `exhaustive:` and `overlapping:` accept `:error` (default), `:warn`
796
+ or `:ignore` (`true`/`false` work too), per function or globally through
797
+ `HaskellMatch.exhaustive = :warn`; `deep: true` selects deep mode (see the
798
+ recursion notes); `ractor: true` makes the function Ractor-shareable. A non-exhaustive function compiled with
799
+ `exhaustive: false` raises `HaskellMatch::MatchError` when no clause matches.
800
+
801
+ ### `HaskellMatch.case_of` — `case ... of` expressions
802
+
803
+ ```ruby
804
+ HaskellMatch.case_of(value) do
805
+ on("Just x", guard: ->(x) { x > 10 }) { |x| "big #{x}" }
806
+ on("Just x") { |x| "just #{x}" }
807
+ on("Nothing") { "nothing" }
808
+ end
809
+ ```
810
+
811
+ Several scrutinees may be given: `case_of(a, b) { on("(x:_)", "True") { ... } }`.
812
+ The decision tree for each distinct set of patterns is compiled once and
813
+ cached; the blocks are collected on each evaluation, so `fn` is the fast path
814
+ for hot code.
815
+
816
+ ### `HaskellMatch.pattern` — single patterns
817
+
818
+ ```ruby
819
+ p = HaskellMatch.pattern("Just (x:rest)") # also HaskellMatch["..."]
820
+ p.match(Just.new([1, 2, 3])) # => { x: 1, rest: [2, 3] }
821
+ p.match(Nothing) # => nil
822
+ p.match!(Nothing) # raises MatchError
823
+ p === Just.new([1]) # => true, so it works in case/when
824
+ p.irrefutable? # => false
825
+ ```
826
+
827
+ ### Methods: `hdef`
828
+
829
+ ```ruby
830
+ class Account
831
+ extend HaskellMatch::DSL # fn, case_of, pattern, data, hdef
832
+ include Maybe
833
+
834
+ hdef :apply do
835
+ on("Nothing") { self }
836
+ on("Just amt", guard: ->(amt) { amt <= limit }) { |amt| Account.new(balance + amt) }
837
+ on("Just amt") { |amt| raise ArgumentError, "over limit: #{amt}" }
838
+ end
839
+ end
840
+ ```
841
+
842
+ ## Ruby values, Haskell discipline
843
+
844
+ Everything above works on values you declare with `HaskellMatch.data`. This
845
+ section is about the rest of Ruby: Hashes, the `Data` and `Struct` classes
846
+ you already have, and the conveniences Haskell programmers expect.
847
+
848
+ ### Hash patterns
849
+
850
+ A Ruby Hash literal is a Hash pattern (quoted: `{name = n, "key" = p}`).
851
+ It matches a Hash that has every listed key (Symbol or String), with each
852
+ value matched by its sub-pattern; other keys are ignored, like fields left
853
+ out of a record pattern. A key whose value is `nil` counts as present.
854
+
855
+ ```ruby
856
+ greet = HaskellMatch.fn(:greet) do
857
+ on({ name: n, title: t }) { |n, t| "#{t} #{n}" }
858
+ on({ name: n }) { |n| "hi #{n}" }
859
+ on({}) { "anonymous" } # {} matches every Hash
860
+ end
861
+ greet.(name: "Al", title: "Dr") # => "Dr Al"
862
+ greet.(name: "Al", age: 3) # => "hi Al"
863
+ ```
864
+
865
+ Exhaustiveness and redundancy are checked as for everything else: without
866
+ a `{}` or `_` clause the checker reports `p1 where p1 is a Hash without the
867
+ key name`; a clause needing a superset of an earlier clause's keys is
868
+ redundant; a Hash column cannot be mixed with constructors or literals.
869
+ Values nest (`{ user: { id: i } }`, `{ k: Just(x) }`), and a non-Hash
870
+ argument with no wildcard clause is a `TypeMismatchError`.
871
+
872
+ ### Ruby classes as constructors
873
+
874
+ A `Data` or `Struct` class visible from the definition needs no
875
+ declaration: naming it in a pattern makes it a type with that single
876
+ constructor, so one clause covers it and its members are its fields (record
877
+ patterns included).
878
+
879
+ ```ruby
880
+ Point = Data.define(:x, :y)
881
+ norm = HaskellMatch.fn(:norm) { on(Point(x, y)) { |x, y| Math.hypot(x, y) } }
882
+ norm.(Point.new(3, 4)) # => 5.0
883
+ HaskellMatch.pattern("Point { x = a }").match(Point.new(1, 2)) # => {a: 1}
884
+ ```
885
+
886
+ For several classes that together form one closed type (a sealed
887
+ hierarchy), `HaskellMatch.sealed` lists them. `Data` and `Struct` classes
888
+ bring their own field names; for any other class, name the reader methods
889
+ that are its fields.
890
+
891
+ ```ruby
892
+ Circle = Data.define(:r)
893
+ Rect = Data.define(:w, :h)
894
+ class Tri
895
+ attr_reader :a, :b, :c
896
+ def initialize(a, b, c) = (@a, @b, @c = a, b, c)
897
+ end
898
+
899
+ Shape = HaskellMatch.sealed(:Shape, Circle, Rect, Tri => %i[a b c])
900
+
901
+ area = HaskellMatch.fn(:area) do
902
+ on(Circle(r)) { |r| Math::PI * r * r }
903
+ on("Rect w h") { |w, h| w * h }
904
+ # on("Tri a b c") missing:
905
+ end
906
+ # HaskellMatch::NonExhaustiveError: Patterns not matched: Tri _ _ _
907
+ ```
908
+
909
+ The classes themselves are untouched apart from gaining a few readers
910
+ (`constructor_name`, `field_names`, `data_type`). Instances are identified
911
+ by exact class, so list the leaf classes of a hierarchy. The returned type
912
+ module works like any other (`Shape === value`, `Shape.constructors`) and
913
+ is defined as a constant only when you pass `under:`.
914
+
915
+ ### `deriving (Ord, Enum, Bounded)`
916
+
917
+ `Eq` and `Show` always hold: constructor values compare structurally and
918
+ `inspect` prints Haskell. Deriving the other standard classes adds:
919
+
920
+ ```ruby
921
+ HaskellMatch.data "Color = Red | Green | Blue deriving (Eq, Ord, Enum, Bounded, Show)"
922
+ include Color
923
+
924
+ Red < Blue # => true (Ord: constructor order, then fields)
925
+ [Blue, Red, Green].sort # => [Red, Green, Blue]
926
+ Red.succ # => Green (Enum; Blue.succ raises, as in GHC)
927
+ (Red..Blue).to_a # => [Red, Green, Blue]
928
+ Color.enum_from(Green) # => [Green, Blue]
929
+ Color.enum_from_then_to(Red, Blue, Blue) # => [Red, Blue]
930
+ Color.min_bound # => Red (Bounded)
931
+ Green.from_enum # => 1
932
+ ```
933
+
934
+ `Ord` works for any type (`Pt 1 2 < Pt 1 3`); `Enum` and `Bounded` need an
935
+ enumeration (every constructor nullary), as GHC requires. The Hash form
936
+ takes `deriving: %i[Ord Enum]`. Unsupported class names are an error, and
937
+ nothing is registered when a derivation fails.
938
+
939
+ ### Checked field types
940
+
941
+ The types written in a declaration are documentation by default. With
942
+ `check_types: true` (or `HaskellMatch.check_field_types = true` for all
943
+ declarations) the constructor verifies each argument and raises
944
+ `FieldTypeError` otherwise:
945
+
946
+ ```ruby
947
+ HaskellMatch.data "Person = Person { name :: String, age :: Int, boss :: Maybe Person }",
948
+ check_types: true
949
+ Person.new("Al", "3", Nothing)
950
+ # HaskellMatch::FieldTypeError: Person: field 'age' expects Int, got "3" (String)
951
+ ```
952
+
953
+ `Int`/`Integer` take Integers; `Double`/`Float` any Numeric; `String` a
954
+ String or list of characters; `Char` a one-character String; `Bool`
955
+ true/false; `[a]` any list (Array, String, LazyList, Enumerator);
956
+ `(a, b)` an Array of that size, elementwise; `a -> b` anything callable; a
957
+ declared type (`Maybe Person`, `Shape`) a value of that type; any other
958
+ capitalised name a Ruby class or module of that name when one exists
959
+ (`Time`, `Hash`, `MyApp::Money`); type variables anything.
960
+
961
+ ### `where` helpers
962
+
963
+ A local function inside a definition, like a Haskell `where` binding. It is
964
+ a full `Function` (checked, with `tail` and `defer`), compiled with the
965
+ enclosing function's options unless you override them, and reachable by
966
+ name from every clause body and from the other helpers.
967
+
968
+ ```ruby
969
+ sum_to = HaskellMatch.fn(:sum_to) do
970
+ on(n) { |n| go.(n, 0) }
971
+ where :go do
972
+ on(0, acc) { |acc| acc }
973
+ on(k, acc) { |k, acc| go.tail(k - 1, acc + k) }
974
+ end
975
+ end
976
+ sum_to.(1_000_000) # => 500000500000
977
+ sum_to.helpers # => {"go" => #<HaskellMatch::Function go/2>}
978
+ ```
979
+
980
+ Helpers do not see the enclosing clause's variables (pass them as
981
+ arguments, as the example does), and in `ractor: true` mode they cannot
982
+ call back into the enclosing function by name.
983
+
984
+ ### Composition
985
+
986
+ `Function#>>` and `#<<` compose like `Proc`'s: `(f >> g).(x)` is
987
+ `g.(f.(x))` (Haskell's `g . f`) and `(f << g).(x)` is `f.(g.(x))`.
988
+
989
+ ### Errors point at the problem
990
+
991
+ Pattern syntax errors show the pattern with a caret under the offending
992
+ column, and Haskell syntax errors show the offending line of the Ruby or
993
+ `.hs` file:
994
+
995
+ ```
996
+ HaskellMatch::PatternSyntaxError: clause 1: expected ')' but reached end of pattern
997
+ Just (x
998
+ ^
999
+ ```
1000
+
1001
+ ## Inline Haskell and `.hs` files
1002
+
1003
+ `HaskellMatch.haskell(source)` compiles Haskell source into a module and
1004
+ returns it; `into: SomeModule` compiles into an existing one, and inside a
1005
+ module body `extend HaskellMatch::Haskell` gives a `haskell` method that
1006
+ does the same. `HaskellMatch.load(path)` compiles a file into a fresh
1007
+ module; `HaskellMatch.require(name)` finds `name.hs` on `$LOAD_PATH` (or
1008
+ takes a path), compiles it once, and defines a constant for it named after
1009
+ the file's `module` header (`module Data.Tree where` becomes `Data::Tree`)
1010
+ or, without one, the camel-cased file name.
1011
+
1012
+ ```ruby
1013
+ Geometry = HaskellMatch.load("examples/geometry.hs")
1014
+ HaskellMatch.require "lists" # lists.hs somewhere on $LOAD_PATH -> Lists
1015
+ HaskellMatch.require "lib/hs/tree", under: MyApp # -> MyApp::Tree (or its module header)
1016
+ ```
1017
+
1018
+ Write the source in a heredoc; use a quoted heredoc (`<<~'HS'`) when the
1019
+ Haskell contains backslashes (lambdas) so Ruby leaves them alone.
1020
+
1021
+ ### The supported language
1022
+
1023
+ The front end is a Haskell 2010 parser with the layout rule, so ordinary
1024
+ Haskell formatting works. Supported:
1025
+
1026
+ * `data` and `newtype` declarations, positional, infix (`data V = Double
1027
+ :| Double`) or with record fields (whose names are selector functions,
1028
+ with `P { f = e }` construction and `p { f = e }` update), with
1029
+ `deriving` (`Eq` and `Show` always hold; `Ord`, `Enum` and `Bounded` work
1030
+ as described under [deriving](#deriving-ord-enum-bounded), so `succ c`,
1031
+ `[Red ..]`, `minBound`-style code runs); `type` synonyms and signatures
1032
+ (accepted and ignored: Ruby is the type system here).
1033
+ * `module M (exports) where` headers and `import` declarations: see
1034
+ [Modules](#modules-imports-and-exports) below.
1035
+ * User-defined operators with `infixl`/`infixr`/`infix` fixity
1036
+ declarations, in prefix form (`(<+>) a b = ...`), infix form
1037
+ (`a <+> b = ...`) or with backticks (``x `cons` xs = ...``), at top level
1038
+ or in `where`/`let`; operators and backticked functions in sections, as
1039
+ values (`(<+>)`) and from Ruby (`Mod.send(:"<+>", a, b)`,
1040
+ `Mod.haskell_function("<+>")`).
1041
+ * Function equations with any patterns this library supports (constructors,
1042
+ infix constructors, literals, negative literals, characters, strings,
1043
+ lists, tuples, `_`, as-patterns, lazy and bang patterns, records), guards
1044
+ with `otherwise`, pattern guards (`| Just v <- lookup k m, v > 0 = ...`)
1045
+ and `let` guards, `where` bindings (functions, values and pattern
1046
+ bindings, nested arbitrarily), `let ... in`, `case ... of` with guards,
1047
+ `if/then/else`.
1048
+ * Expressions: application and partial application, operators with the
1049
+ Prelude's fixities, backtick operators, sections (`(*2)`, `` (`div` 2) ``,
1050
+ `subtract 1`), operator values (`(+)`), `$`, `.`, lambdas (including
1051
+ pattern lambdas), tuples, list literals, ranges (`[1..n]`, `[1,3..]`,
1052
+ `[0..]`), list comprehensions with generators, guards and `let`.
1053
+ * The small syntax extensions GHC users reach for without thinking:
1054
+ `MultiWayIf` (`if | c1 -> e1 | c2 -> e2`), `LambdaCase` (`\case`),
1055
+ `TupleSections` (`(,x)`, `(1,,3)`), `NamedFieldPuns` in construction.
1056
+ * Literals: decimal, hexadecimal, octal and binary integers, exponent
1057
+ floats, `_` digit separators, and every Haskell character escape
1058
+ (`\n`, `\x41`, `\o101`, `\65`, `\SOH`, `\^A`, `\&`, string gaps).
1059
+ * Names: `camelCase` functions get a `snake_case` alias; a trailing prime
1060
+ becomes `_prime` (`foldl'` is callable as `foldl_prime`).
1061
+ * Top-level values (`primes = ...`) become memoised methods; a value of
1062
+ function type can still be called with arguments from Ruby
1063
+ (`Mod.from_list([1, 2])` when `fromList = foldr insert Leaf`).
1064
+
1065
+ Out of scope, by design: type classes (`class`/`instance`), `do` notation
1066
+ and monads, and anything needing type inference. They are rejected with a
1067
+ clear message.
1068
+
1069
+ ### Modules, imports and exports
1070
+
1071
+ A compiled module can import another. Functions and values of the imported
1072
+ module become callable (the compiler uses them with their real arity, tail
1073
+ calls included), its data types join the importing module's type scope so
1074
+ their constructors work in patterns with full exhaustiveness checking, and
1075
+ the constructors become constants of the importing module.
1076
+
1077
+ ```haskell
1078
+ module Physics where
1079
+ import Vectors -- Vectors.hs on $LOAD_PATH, or the constant Vectors
1080
+ import Vectors (Vec(..), norm) -- only these
1081
+ import Vectors hiding (hidden) -- all but these
1082
+ import qualified Data.Map as M -- qualification is accepted and ignored
1083
+ import Math (hypot) -- a plain Ruby module: its singleton methods
1084
+ ```
1085
+
1086
+ A module name resolves to an existing constant (`Data.Tree` is
1087
+ `Data::Tree`) or to a file on `$LOAD_PATH` (`Data/Tree.hs`, `Data.Tree.hs`
1088
+ or `data/tree.hs`), loaded once with `HaskellMatch.require`. The standard
1089
+ library modules (`Data.List`, `Data.Char`, `Data.Maybe`, `Control.Monad`,
1090
+ ...) are the Prelude: importing one only checks that the named items exist.
1091
+
1092
+ An export list (`module Vectors (Vec(..), (<+>), norm) where`) limits what
1093
+ importers see; `Mod.haskell_exports` returns it. Everything stays callable
1094
+ from Ruby regardless. Compiled code refers to names unqualified, so two
1095
+ qualifications of the same name are not distinguished.
1096
+
1097
+ ### Semantics worth knowing
1098
+
1099
+ * **Strictness.** Compiled code is strict (it is Ruby), with one deliberate
1100
+ exception: the tail of `x : e` is deferred when `e` is a computation
1101
+ rather than a variable or literal. That is exactly what makes
1102
+ `p : sieve xs` and `fibs = 0 : 1 : zipWith (+) fibs (tail fibs)` work. A
1103
+ list built that way is a `LazyList` (`to_a` materialises it, `==`
1104
+ compares elements); a list built from a variable tail (`toUpper c : cs`)
1105
+ keeps its input's type, so Strings stay Strings and Arrays stay Arrays.
1106
+ * **Lists are Arrays, Strings or LazyLists**, exactly as for patterns.
1107
+ Prelude functions accept all three and stay lazy when their input is.
1108
+ Ranges are Arrays when bounded and lazy when not.
1109
+ * **Recursion** compiles to the same machinery as `HaskellMatch.fn`: calls
1110
+ in tail position use `tail` (constant space), everything else uses the
1111
+ segmented stack, and `HaskellMatch.max_depth` applies.
1112
+ * **Exhaustiveness and redundancy** are enforced for every function,
1113
+ including `where`/`let` helpers, `case` expressions and pattern lambdas,
1114
+ with the policy you pass as `exhaustive:` (default
1115
+ `HaskellMatch.exhaustive`). Errors name the Haskell function and the line
1116
+ of the Ruby file (or `.hs` file) it came from.
1117
+ * **Type scopes.** Each compiled module gets its own type scope: a snapshot
1118
+ of the global `HaskellMatch.data` registry when the module is first
1119
+ compiled plus its own `data` declarations, so two modules can both declare
1120
+ a `Shape`. Ruby code matches on a module's types through the module's
1121
+ `fn`, `case_of`, `pattern` and `data` methods, which take the same
1122
+ options as `HaskellMatch`'s. Types you want to share between Ruby and
1123
+ Haskell are simplest declared globally with `HaskellMatch.data` before
1124
+ the module is compiled.
1125
+ * **Interop.** A name the Haskell does not define (and the Prelude does not
1126
+ provide) is called as a method of the host module, so a module can mix
1127
+ `def self.helper` with Haskell that calls `helper`. Ruby lambdas, Procs
1128
+ and Methods are Haskell functions (`Mod.my_map(->(x) { x * 2 }, [1, 2])`),
1129
+ a function returned from Haskell is a Ruby `Proc`, and calling an exported
1130
+ function applies like Haskell: `Mod.add3(1, 2, 3)`, `Mod.add3(1).(2).(3)`
1131
+ (a partial application) and `Mod.adder(5, 10)` (extra arguments go to the
1132
+ returned function) all work. The compiled `Function` objects are available
1133
+ as `Mod.haskell_functions` / `Mod.haskell_function(:name)` for `tail`,
1134
+ `to_proc`, `curried`, `===` and `decision_tree`.
1135
+ * **The Prelude** lives in `HaskellMatch::Prelude` and is callable from
1136
+ Ruby too (`HaskellMatch::Prelude.take(3, xs)`). It provides the standard
1137
+ types `Maybe`, `Either` and `Ordering` (`HaskellMatch::Prelude::Maybe::Just`)
1138
+ and the usual functions: `map`, `filter`, `foldr`, `foldl`, `zip`,
1139
+ `zipWith`, `take`, `drop`, `takeWhile`, `iterate`, `repeat`, `cycle`,
1140
+ `sum`, `product`, `length`, `reverse`, `concat`, `concatMap`, `elem`,
1141
+ `lookup`, `words`, `lines`, `show`, `fromIntegral`, `div`, `mod`,
1142
+ `compare`, `maybe`, `fromMaybe`, `either`, `error`, `undefined`, the
1143
+ `Data.Char` basics and more. `HaskellMatch::Prelude::ARITY.keys` lists
1144
+ them all.
1145
+ * **Debugging.** `HaskellMatch::Haskell.generated_ruby(mod)` returns the
1146
+ Ruby a module was compiled to.
1147
+
1148
+ ## Deep and infinite recursion: expert notes
1149
+
1150
+ This section is for readers who intend to recurse hundreds of thousands or
1151
+ millions of levels deep, or to iterate forever, and want to know exactly what
1152
+ happens underneath. The short version: haskell_match gives you GHC's
1153
+ behaviour (recursion limited by memory, loops in constant space) on top of a
1154
+ VM whose stack is fixed, and the price of that is paid in memory and in the
1155
+ GC. Every number below was measured on Ruby 3.3.6, x86-64 Linux, with the
1156
+ default settings; reproduce them with the snippets at the end.
1157
+
1158
+ ### What a call costs on the stacks
1159
+
1160
+ A call `f.(x)` enters the native `call` method (one Ruby control frame for the
1161
+ C function), matches `x` against the decision tree with no allocation, and
1162
+ invokes the selected clause body with `rb_proc_call_with_block`. The body is
1163
+ a Ruby block, so it gets a second control frame. If the body itself calls
1164
+ `f.(y)`, the whole sequence nests. Each level therefore consumes:
1165
+
1166
+ * **Ruby VM stack**: two control frames (the C-function frame and the block
1167
+ frame) plus the block's locals and operand stack, roughly 150–300 bytes.
1168
+ * **Machine (C) stack**: the native method's own frame, the VM re-entry
1169
+ (`vm_exec`) that runs the block, and the Rust matcher's scratch space,
1170
+ roughly a kilobyte.
1171
+
1172
+ Ruby sizes both stacks when a thread or fiber is created and never grows
1173
+ them: 1 MB VM / 1 MB machine for a thread, 128 KB VM / 512 KB machine for a
1174
+ fiber (`RubyVM::DEFAULT_PARAMS`). A plain Ruby lambda that recurses with one
1175
+ frame per level dies at about 11,000 levels on the main thread; a clause body
1176
+ with two frames per level would die at about 7,000; inside a fiber, at about
1177
+ 480. Nothing at run time can enlarge an existing stack, which is why the
1178
+ mechanisms below exist.
1179
+
1180
+ ### Mechanism 1: stack segments (what plain recursion uses)
1181
+
1182
+ The native `call` keeps a per-thread nesting counter. When a body is about to
1183
+ run at a depth that is a multiple of `HaskellMatch.stack_segment` (default
1184
+ 100), the body is run inside a brand-new Fiber instead of on the current
1185
+ stack. That Fiber has fresh 128 KB / 512 KB stacks; the recursion continues
1186
+ in it until another 100 levels, when the next segment is started. Segments
1187
+ form a linked chain of suspended fibers, each waiting for the inner one's
1188
+ result, which is exactly the shape of a growable stack.
1189
+
1190
+ Facts about segments:
1191
+
1192
+ * **Default 100 is deliberate.** A single fiber holds about 480 plain levels;
1193
+ bodies that call a few helper methods per level use more VM stack, so 100
1194
+ leaves a safety factor of four to five. Raising `stack_segment` lowers the
1195
+ memory per level (fewer, fuller fibers) but narrows that margin: 400 and
1196
+ above overflow a fiber with even the simplest body. Lowering it is always
1197
+ safe and only costs memory and fiber creations.
1198
+ * **Memory per level: about 1.6 KB.** Each segment commits its 128 KB fiber
1199
+ VM stack in full (the VM touches both ends of it), so the per-level cost is
1200
+ dominated by `128 KB / stack_segment`, not by the frames themselves. 200k
1201
+ levels peak at about 300 MB; 1M levels at about 1.6 GB. GHC, by comparison,
1202
+ spends about 24 bytes per level of `1 + length xs`. The shape is the same
1203
+ as Haskell's (non-tail recursion is linear in depth); the constant is about
1204
+ sixty times worse.
1205
+ * **Memory is reclaimed, lazily.** When the outermost call returns, every
1206
+ segment fiber has finished and is unreferenced; the Fiber objects are
1207
+ collected at the next GC and their stacks go back to Ruby's fiber pool,
1208
+ which marks the pages `MADV_FREE`. The kernel reclaims those pages under
1209
+ memory pressure, so RSS stays high after a deep call even though the
1210
+ memory is available (`LazyFree` in `/proc/self/smaps_rollup` shows it:
1211
+ 288 MB of a 325 MB RSS after a 200k-deep call). Repeated deep calls reuse
1212
+ the pooled stacks and do not grow RSS further. This is not a leak; it is
1213
+ the pool keeping what it once needed.
1214
+ * **First call is slow, later calls are fast.** Committing fresh fiber stacks
1215
+ page-faults every page: the first 400k-deep call took 3.2 s, the second
1216
+ 0.6 s, the third 0.35 s (0.9 µs per level); at 1M depth, 8.9 s then 2.8 s.
1217
+ Budget for the cold run if a deep recursion happens once.
1218
+ * **GC cost grows with live depth.** Fiber objects are not write-barrier
1219
+ protected (their stacks change on every instruction, so no barrier could
1220
+ track them), and CRuby therefore re-marks the VM and machine stacks of
1221
+ every suspended segment on every collection, minor ones included. A GC
1222
+ during a pending recursion costs O(live stack bytes): at 200k levels a
1223
+ minor GC takes about 165 ms instead of 2 ms. Nothing outside CRuby can
1224
+ change *that* a suspended fiber is rescanned; what can be changed is how
1225
+ much there is to scan, and that is what deep mode below does (28 ms for the
1226
+ same collection). `defer` avoids the fiber stacks altogether, and
1227
+ `GC.disable` around a known deep computation, or a larger
1228
+ `RUBY_GC_HEAP_INIT_SLOTS`, reduces the number of collections.
1229
+ * **Semantics across a segment boundary.** Exceptions propagate normally
1230
+ (`rb_fiber_resume` re-raises them in the parent), but the backtrace only
1231
+ covers the innermost segment. Non-local exits do not cross: `throw` to a
1232
+ `catch` outside the segment raises `UncaughtThrowError`, and `break` or
1233
+ `return` out of a body proc raises `LocalJumpError` at the boundary.
1234
+ Within 100 levels of the `catch`, everything behaves as in one stack.
1235
+ `Fiber.yield` inside a body yields the segment fiber, not yours.
1236
+ * **Threads and Ractors.** The depth counter is per OS thread, so each Ruby
1237
+ Thread and each Ractor recurses independently. If your own Fiber runs a
1238
+ deep recursion, the counter is shared with the thread that created it; the
1239
+ only effect is that segments may start a little earlier than needed.
1240
+
1241
+ ### Deep mode: the same recursion with no native frames on the stack
1242
+
1243
+ Where do the 1.6 KB per level come from, given that an idle fiber commits only
1244
+ about 13 KB? From the C frames. In the default (native) mode the body is
1245
+ invoked by the Rust matcher: the machine stack holds, per level, the C
1246
+ function frame of `call`, the VM re-entry that runs the block, and the
1247
+ matcher's scratch space, about 1.3 KB, all of which the GC also scans
1248
+ conservatively because it cannot know which words are references. The Ruby
1249
+ frames themselves are small.
1250
+
1251
+ `deep: true` moves the body invocation into Ruby:
1252
+
1253
+ ```ruby
1254
+ count = HaskellMatch.fn(:count, deep: true) do
1255
+ on([]) { 0 }
1256
+ on([_, *xs]) { |xs| 1 + count.(xs) }
1257
+ end
1258
+ ```
1259
+
1260
+ `call` becomes a generated Ruby method of exact arity that asks the native
1261
+ `prepare` for the selected body and its bound values, then calls the body
1262
+ itself. A Ruby-to-Ruby call pushes no C frame, so while the recursion is
1263
+ pending the machine stack holds nothing of ours; segments, `tail`, `defer`
1264
+ and `max_depth` work exactly as before (the trampoline is reimplemented in
1265
+ Ruby, with the same chunked continuation stack). Measured against native
1266
+ mode at 200k levels:
1267
+
1268
+ | mode | shallow call | memory per level | minor GC at 200k depth | 200k levels, warm |
1269
+ |--------|-------------:|-----------------:|-----------------------:|------------------:|
1270
+ | native | ~230 ns | ~1.6 KB | ~165 ms | 0.94 s |
1271
+ | deep | ~460 ns | ~105 B | ~28 ms | 0.29 s |
1272
+
1273
+ A million plain levels in deep mode take about 3 s and 300 MB. The trade is
1274
+ clear-cut: deep mode costs an extra Ruby frame on *every* call, which doubles
1275
+ the time of a shallow call, and in exchange brings a level within a factor of
1276
+ four of GHC's 24 bytes and cuts GC marking six-fold. Native mode remains the
1277
+ default because most functions never recurse; set `deep: true` on the ones
1278
+ that do, or `HaskellMatch.deep_by_default = true` for a codebase that is
1279
+ recursive throughout. A deep-mode function may call native-mode functions
1280
+ and vice versa, including through `tail`.
1281
+
1282
+ ### Mechanism 2: tail calls (constant space)
1283
+
1284
+ ```ruby
1285
+ go = HaskellMatch.fn(:go) do
1286
+ on(acc, []) { |acc| acc }
1287
+ on(acc, [x, *xs]) { |acc, x, xs| go.tail(acc + x, xs) }
1288
+ end
1289
+ ```
1290
+
1291
+ `go.tail(args...)` returns a `HaskellMatch::TailCall` marker. The native
1292
+ `call` sees it, replaces the current arguments with the marker's and matches
1293
+ again on the same frame; nothing is pushed on any stack and nothing counts
1294
+ towards `max_depth`. The target may be a different function, so mutual
1295
+ recursion (`even`/`odd`) loops in constant space too. Cost: one small `Data`
1296
+ allocation per iteration, about 1.1 µs per level including the list slice
1297
+ (a shared, copy-on-write subarray). This is the right tool for anything that
1298
+ is a loop in Haskell: accumulators, folds, state machines, servers. A marker
1299
+ returned anywhere but as the body's final value is just a value (`go.tail(1)`
1300
+ outside a call is an ordinary object).
1301
+
1302
+ ### Mechanism 3: deferred calls (cheap depth)
1303
+
1304
+ ```ruby
1305
+ length = HaskellMatch.fn(:length) do
1306
+ on([]) { 0 }
1307
+ on([_, *xs]) { |xs| length.defer(xs) { |n| 1 + n } } # 1 + length xs
1308
+ end
1309
+ ```
1310
+
1311
+ `f.defer(args...) { |result| ... }` is a tail call that carries a
1312
+ continuation. The native `call` pushes the block on a stack it owns and
1313
+ continues with the call; when a body finally returns an ordinary value, the
1314
+ pending blocks are applied to it last-in first-out, each possibly returning
1315
+ another marker. The recursion's stack lives on the heap, so it is bounded by
1316
+ memory alone and `max_depth` never triggers.
1317
+
1318
+ That stack is deliberately a chain of 256-element Ruby arrays rather than one
1319
+ growing array. Ruby's GC is generational: an old array that is written to is
1320
+ put on the remembered set and rescanned in full by every minor collection, so
1321
+ a single million-element stack would have made each GC O(depth). A full chunk
1322
+ is never written again, gets promoted, and is skipped by minor GCs; only the
1323
+ current chunk is rescanned. The difference is large: 2M levels took 31 s with
1324
+ one array and 8.9 s with chunks (1M: 5.4 s vs 3.8 s), and peak memory fell
1325
+ from 770 MB to 470 MB because the old array's doubling growth is gone.
1326
+
1327
+ Per level `defer` costs one Proc plus its environment (about 200 bytes) and
1328
+ about 4 µs, mostly the allocation and the minor GCs it triggers (878 minor
1329
+ GCs over a 2M-deep run, each cheap). Prefer `defer` over plain recursion when
1330
+ depth is known to be large and memory matters; prefer plain recursion when it
1331
+ is not, since `defer` requires writing the continuation by hand and runs the
1332
+ continuation outside the body's frame (`self` and closure variables are those
1333
+ of the block, as usual).
1334
+
1335
+ ### The depth guard
1336
+
1337
+ `HaskellMatch.max_depth` (default 250,000) bounds the nesting counter from
1338
+ mechanism 1. Past it, the next nested call raises
1339
+ `HaskellMatch::StackOverflowError` instead of allocating another segment. At
1340
+ 1.6 KB per level the default corresponds to about 400 MB, the point of the
1341
+ limit being that a runaway recursion fails loudly rather than exhausting the
1342
+ machine, which is what GHC's stack limit (80% of RAM by default) is for too.
1343
+ Set it higher or to 0 (unlimited) for a computation that legitimately needs
1344
+ more; `tail` and `defer` never count towards it. The counter is restored
1345
+ exactly even when a body leaves by exception, so a caught error deep in a
1346
+ recursion does not shift later limits.
1347
+
1348
+ ### Choosing
1349
+
1350
+ | Pattern of recursion | Use | Space | Time per level (warm) |
1351
+ |-------------------------------------------|--------------------------|-------------|-----------------------|
1352
+ | Loop with accumulator, fold, state machine| `f.tail(...)` | O(1) | ~1.1 µs |
1353
+ | Deep non-tail recursion, depth known large| `f.defer(...) { }` | ~200 B/level| ~4 µs |
1354
+ | Ordinary recursion, depth moderate | plain `f.(...)` | ~1.6 KB/level (≤ `max_depth`) | ~0.9 µs (first call slower) |
1355
+ | Ordinary recursion, depth large | `deep: true` + plain `f.(...)` | ~105 B/level (≤ `max_depth`) | ~1.5 µs, half the GC time |
1356
+ | Infinite data | `HaskellMatch.lazy` + patterns | per element forced | per element |
1357
+
1358
+ Infinite recursion in the Haskell sense, a producer that never returns, is
1359
+ expressed as a lazy list consumed by `tail`-recursive or bounded consumers:
1360
+ `take.(n, HaskellMatch::LazyList.iterate(1) { |x| x * 2 })` forces exactly
1361
+ `n` cells and no more, and each forced cell is memoised, so sharing works as
1362
+ in Haskell (two consumers of the same list see the same elements, computed
1363
+ once). An Enumerator passed directly is re-wrapped on each call, iterating
1364
+ from its start, which keeps calls referentially transparent at the cost of
1365
+ recomputing from scratch per call; keep a `LazyList` in a variable when the
1366
+ elements are expensive.
1367
+
1368
+ ### Reproducing the measurements
1369
+
1370
+ ```ruby
1371
+ require "haskell_match"
1372
+ HaskellMatch.max_depth = 0
1373
+ count = HaskellMatch.fn(:count) { on("[]") { 0 }; on("(_:xs)") { |xs| 1 + count.(xs) } }
1374
+ dlen = HaskellMatch.fn(:dlen) { on("[]") { 0 }; on("(_:xs)") { |xs| dlen.defer(xs) { |n| 1 + n } } }
1375
+ go = HaskellMatch.fn(:go) { on("acc", "[]") { |acc| acc }; on("acc", "(x:xs)") { |acc, x, xs| go.tail(acc + x, xs) } }
1376
+
1377
+ rss = -> { File.read("/proc/self/status")[/VmRSS:\s+(\d+)/, 1].to_i / 1024 }
1378
+ list = (1..200_000).to_a
1379
+ before = rss.(); count.(list); puts "segments: +#{rss.() - before} MB" # ~300 MB, ~1.6 KB/level
1380
+ before = rss.(); dlen.(list); puts "defer: +#{rss.() - before} MB" # ~45 MB
1381
+ before = rss.(); go.(0, list); puts "tail: +#{rss.() - before} MB" # ~0 MB
1382
+ GC.start; puts File.read("/proc/self/smaps_rollup")[/LazyFree:.*/] # reclaimable pages
1383
+ ```
1384
+
1385
+ ## Errors
1386
+
1387
+ All errors inherit from `HaskellMatch::Error`.
1388
+
1389
+ Definition time (`HaskellMatch::CompileError`): `PatternSyntaxError`,
1390
+ `UnknownConstructorError`, `ArityError` (constructor applied to the wrong
1391
+ number of patterns), `PatternTypeError` (one position matched against two
1392
+ types), `DuplicateVariableError`, `FieldError`, `DataDeclarationError`,
1393
+ `ClauseArityError`, `NonExhaustiveError` (`#missing` lists the witnesses),
1394
+ `RedundantClauseError` (`#clauses` lists the indices), `DefinitionError`.
1395
+
1396
+ Match time (`HaskellMatch::MatchError`): `MatchError` itself for a partial
1397
+ function with no matching clause, `TypeMismatchError` when a value is of a
1398
+ type no pattern of the function can accept (`expected a value of type Maybe
1399
+ but got 5 (Integer)`), `IrrefutablePatternError` when a `~` pattern fails to
1400
+ destructure. `HaskellMatch::StackOverflowError` is raised past
1401
+ `HaskellMatch.max_depth`. Wrong argument counts raise `ArgumentError`.
1402
+
1403
+ Exceptions raised in bodies and guards propagate unchanged, with their
1404
+ backtraces; `throw`, `return` and `next` behave as in any block.
1405
+
1406
+ ## Concurrency
1407
+
1408
+ * **Threads**: safe. Native code runs under the GVL, the type registry is
1409
+ behind a mutex that is never held while calling back into Ruby, compiled
1410
+ functions are immutable, and per-call state lives on the stack.
1411
+ * **Fibers**: safe; no per-thread or per-fiber state is kept natively.
1412
+ * **Ractors**: the extension is declared Ractor-safe and data values are
1413
+ shareable. A function is shareable when defined with `ractor: true`, which
1414
+ makes its clause procs shareable (their `self` becomes an inert frozen
1415
+ object and any local variables they capture must already be shareable) and
1416
+ passes the function through `Ractor.make_shareable`:
1417
+
1418
+ ```ruby
1419
+ f = HaskellMatch.fn(:f, ractor: true) { on("Just x") { |x| x }; on("Nothing") { 0 } }
1420
+ Ractor.new(f) { |g| g.(Just.new(1)) }.take # => 1
1421
+ ```
1422
+
1423
+ Inside a shareable function, recursion goes through the function's name or
1424
+ `recur` (the body's `self` is a module that knows the finished function).
1425
+ Do not also keep the function in a local variable of the same name: the
1426
+ variable would shadow the name, and `make_shareable` fixes a captured
1427
+ local's value (still `nil` at that point).
1428
+
1429
+ ## Performance
1430
+
1431
+ `rake bench` compares against hand-written Ruby (Ruby 3.3, x86-64, one run;
1432
+ numbers are ns per call):
1433
+
1434
+ | benchmark | haskell_match | Ruby `case/in` | hand-written Ruby |
1435
+ |---------------------------------------------|--------------:|---------------:|------------------:|
1436
+ | `from_maybe` (2 args, constructor switch) | 201 | 238 | 97 (`case/when`) |
1437
+ | `area` (3 constructors, nested arithmetic) | 270 | 356 | |
1438
+ | `length` of a 20-element list (recursive) | 7850 | 4686 | 2092 (slices) |
1439
+ | `fib(20)` via literals `0`, `1`, `n` | 3 866 000 | | 1 216 000 (`if`) |
1440
+ | `pattern.match` (bindings hash) | 548 | | |
1441
+ | `case_of` (blocks collected per call) | 14 600 | | |
1442
+
1443
+ The matcher itself is cheap; what remains per call is one native method
1444
+ dispatch plus one Ruby block invocation for the body, which inline Ruby code
1445
+ does not pay. Recursive list functions additionally allocate one shared-slice
1446
+ Array per `(x:xs)` tail that is bound.
1447
+
1448
+ ## How it works
1449
+
1450
+ `ext/haskell_match` is a Rust crate with a pure core and a thin Ruby layer:
1451
+
1452
+ * `core::lexer`, `core::parser` — Haskell pattern and `data` declaration syntax.
1453
+ * `core::types`, `core::resolve` — the type environment and name resolution
1454
+ (arity checks, record fields, variable numbering).
1455
+ * `core::typecheck` — every position across all clauses must have one type.
1456
+ * `core::exhaust` — Maranget's usefulness algorithm: redundancy and the list
1457
+ of unmatched patterns, including literal positions (`p1 where p1 is not one
1458
+ of {0, 1}`).
1459
+ * `core::tree` — compilation to a decision tree over numbered value slots.
1460
+ * `ruby::runtime` — evaluation over Ruby values, including Strings and lazy
1461
+ lists viewed as lists; `ruby` — the `Native` module, the trampoline that
1462
+ handles `tail`/`defer` markers and the Fiber-segmented recursion.
1463
+ * `lib/haskell_match/pattern_ast.rb` — the in-place pattern syntax, rendered
1464
+ to Haskell text; `lazy_list.rb` — memoised lazy lists.
1465
+
1466
+ The pure core has its own `cargo test` suite; the Ruby suite covers the
1467
+ public API, GC stress, threads and Ractors.
1468
+
1469
+ ## License
1470
+
1471
+ Licensed under either of
1472
+
1473
+ * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
1474
+ http://www.apache.org/licenses/LICENSE-2.0)
1475
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
1476
+ http://opensource.org/licenses/MIT)
1477
+
1478
+ at your option.
1479
+
1480
+ ### Contribution
1481
+
1482
+ Unless you explicitly state otherwise, any contribution intentionally submitted
1483
+ for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
1484
+ dual licensed as above, without any additional terms or conditions.