jade-lang 0.3.1 → 0.5.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +164 -0
  3. data/CHANGELOG.md +215 -1
  4. data/README.md +17 -12
  5. data/docs/interop.md +208 -0
  6. data/docs/json.md +163 -0
  7. data/docs/lsp.md +105 -0
  8. data/docs/stdlib.md +69 -0
  9. data/docs/syntax.md +458 -0
  10. data/docs/testing.md +70 -0
  11. data/lib/jade/api.rb +244 -0
  12. data/lib/jade/cli/check.rb +97 -0
  13. data/lib/jade/cli/q.rb +47 -1
  14. data/lib/jade/cli.rb +4 -2
  15. data/lib/jade/codegen/emitter.rb +11 -2
  16. data/lib/jade/codegen/function_call.rb +1 -1
  17. data/lib/jade/codegen/helpers.rb +6 -0
  18. data/lib/jade/codegen/inlines.rb +18 -0
  19. data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
  20. data/lib/jade/codegen.rb +2 -2
  21. data/lib/jade/debug.rb +59 -0
  22. data/lib/jade/decode.rb +332 -212
  23. data/lib/jade/diagnostics/renderer.rb +16 -5
  24. data/lib/jade/frontend/fixity_fixer.rb +3 -2
  25. data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
  26. data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
  27. data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
  28. data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
  29. data/lib/jade/frontend/semantic_analysis/error/variable_not_found.rb +9 -2
  30. data/lib/jade/frontend/semantic_analysis/member_access.rb +11 -1
  31. data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +35 -32
  32. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +32 -50
  33. data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
  34. data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
  35. data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
  36. data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
  37. data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
  38. data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
  39. data/lib/jade/interop/boundary.rb +2 -3
  40. data/lib/jade/interop/runtime.rb +10 -2
  41. data/lib/jade/lsp/converters.rb +2 -15
  42. data/lib/jade/lsp/snippets.rb +21 -3
  43. data/lib/jade/parsing/error.rb +19 -0
  44. data/lib/jade/runtime.rb +1 -0
  45. data/lib/jade/signature.rb +47 -0
  46. data/lib/jade/stdlib/calendar.rb +20 -19
  47. data/lib/jade/stdlib/clock.rb +7 -13
  48. data/lib/jade/stdlib/debug.rb +12 -0
  49. data/lib/jade/stdlib/decimal.rb +3 -16
  50. data/lib/jade/stdlib/decode.rb +57 -12
  51. data/lib/jade/stdlib/encode.rb +26 -0
  52. data/lib/jade/stdlib/intrinsics.rb +12 -1
  53. data/lib/jade/stdlib/result.rb +1 -1
  54. data/lib/jade/stdlib/show.rb +40 -0
  55. data/lib/jade/stdlib/text.rb +131 -0
  56. data/lib/jade/stdlib.rb +5 -2
  57. data/lib/jade/symbol/interop_function.rb +3 -1
  58. data/lib/jade/symbol.rb +2 -2
  59. data/lib/jade/task.rb +2 -3
  60. data/lib/jade/version.rb +1 -1
  61. metadata +19 -3
data/docs/interop.md ADDED
@@ -0,0 +1,208 @@
1
+ # Interop with Ruby
2
+
3
+ Jade has no implicit side effects. All interaction with the outside world goes
4
+ through `uses` blocks, and every port returns a `Task`.
5
+
6
+ ## Declaring a port
7
+
8
+ The `uses` block declares the boundary types; the names come into scope
9
+ unqualified. **Declarations are separated by commas** — a newline alone ends
10
+ the block, so a second entry without a comma before it reads as a parse error
11
+ (`Unexpected token "entries", expected end`).
12
+
13
+ A port takes as many arguments as its type says: `A, B -> Task(…)` is a
14
+ two-argument port, called `entries(key, limit)`. Parenthesising the arguments
15
+ means something else — `(A, B) -> Task(…)` is a *one*-argument port taking a
16
+ tuple, called `entries((key, limit))`.
17
+
18
+ ```jade
19
+ module Store exposing (page)
20
+
21
+ uses KeyValue with
22
+ members : String -> Task(List(String), String),
23
+ entries : String, Int -> Task(List(String), String)
24
+ end
25
+
26
+
27
+ def page(key: String) -> Task(List(String), String)
28
+ entries(key, 20)
29
+ end
30
+ ```
31
+
32
+ On the Ruby side, register the port with `Jade::Port`. The block receives a
33
+ helper `t` for `t.ok(value)` / `t.err(error)`, then one parameter per declared
34
+ argument:
35
+
36
+ ```ruby
37
+ module KeyValue
38
+ extend Jade::Port
39
+
40
+ task :members do |t, key|
41
+ t.ok(REDIS.smembers(key))
42
+ end
43
+
44
+ task :entries do |t, key, limit|
45
+ t.ok(REDIS.lrange(key, 0, limit - 1))
46
+ end
47
+ end
48
+ ```
49
+
50
+ The block must return `t.ok(value)` or `t.err(error)` — never another `Task`.
51
+ Composition (`map`, `and_then`, `sequence`) lives in Jade. (Pick a port module
52
+ name that doesn't shadow a Ruby constant you rely on — `task :members` defines
53
+ `KeyValue.members`.)
54
+
55
+ ## What crosses the boundary
56
+
57
+ A port is the same boundary as a Jade function called from Ruby, pointed the
58
+ other way, and it converts in both directions: **arguments are encoded on the
59
+ way out, and the return value is decoded on the way back**. Ruby sees wire
60
+ values — strings, numbers, hashes, arrays — never Jade's internal
61
+ representation.
62
+
63
+ ```jade
64
+ uses Scheduling with
65
+ shift_months : Instant, Int -> Task(Instant, Never)
66
+ end
67
+ ```
68
+
69
+ ```ruby
70
+ module Scheduling
71
+ extend Jade::Port
72
+
73
+ task :shift_months do |t, iso, months|
74
+ # iso is "2026-08-01T12:00:00Z", not a Jade::Clock::Instant
75
+ t.ok(Time.iso8601(iso).then { |at| (at << -months).iso8601 })
76
+ end
77
+ end
78
+ ```
79
+
80
+ Every argument type therefore needs an `Encodable` instance, the same way every
81
+ `Task` arm needs a `Decodable` one. A type with neither is a compile error
82
+ naming the argument:
83
+
84
+ ```
85
+ Port `shift_months` cannot encode argument 1 (`Shape`): no Encodable instance
86
+ ```
87
+
88
+ Declare the argument as `Decode.Value` to opt out and hand Ruby the value
89
+ untouched — the arg-side counterpart of a `Decode.Value` return arm. `Value` has
90
+ instances on both sides, so the opt-out holds when it's nested too: a
91
+ `List(Value)` argument, or a struct with a `Value` field, crosses element by
92
+ element with each one left alone.
93
+
94
+ ## Calling Jade from Ruby
95
+
96
+ An exposed function gets two callable forms:
97
+
98
+ ```ruby
99
+ # Boundary form — args decoded, return encoded, Task runs eagerly
100
+ Store.page("recent") # => ["ok", ["a", "b"]]
101
+ Store.page!("recent") # => ["a", "b"] (bang form unwraps, raises on err)
102
+
103
+ # Internal form — keeps the Task as a value you can compose
104
+ task = Store::Internal.page("recent")
105
+ task.run # => Jade::Result::Ok[["a", "b"]]
106
+ ```
107
+
108
+ At the boundary, Ruby values are **decoded into Jade values** on the way in and
109
+ **encoded back to Ruby** on the way out. For `Task` functions the ok-arm decoder
110
+ runs against whatever the port returned, so a signature like `Task(User, String)`
111
+ keeps its declared error type — you don't thread a separate `DecodeError`
112
+ through every call.
113
+
114
+ ## When a function is callable from Ruby
115
+
116
+ The unit here is the **function**, not the type. A function is exposed to Ruby
117
+ only when **all of its parameters are `Decodable` and its return type is
118
+ `Encodable`**. If any parameter can't be decoded, or the return can't be
119
+ encoded, that whole function isn't exposed — it compiles fine and its `Internal`
120
+ form still works, but calling the public `Module.fn` from plain Ruby raises
121
+ `Jade::Interop::NotExposed`. The error names the part that disqualified it
122
+ (e.g. `argument 1 of type Shape has no Decodable instance`).
123
+
124
+ What takes a function out of Ruby's reach is a parameter or return type with no
125
+ `Decodable` / `Encodable` instance — a function value, an unbound type variable,
126
+ or a custom union you haven't given an instance.
127
+
128
+ If a port returns something that doesn't decode to the declared type, the
129
+ boundary raises `Jade::Interop::DecodeError`. This is on purpose: a port
130
+ returning the wrong shape is a programming bug, not a runtime condition to
131
+ recover from, so the boundary raises rather than pass on a malformed value.
132
+
133
+ ```jade
134
+ module Users exposing (fetch)
135
+
136
+ struct User = {
137
+ id: Int,
138
+ name: String
139
+ }
140
+
141
+
142
+ uses Backend with
143
+ raw_fetch : Int -> Task(User, String)
144
+ end
145
+
146
+
147
+ def fetch(id: Int) -> Task(User, String)
148
+ raw_fetch(id)
149
+ end
150
+ ```
151
+
152
+ ```ruby
153
+ module Backend
154
+ extend Jade::Port
155
+
156
+ task :raw_fetch do |t, id|
157
+ t.ok({ name: "Paul" }) # oops — missing :id
158
+ end
159
+ end
160
+
161
+ Users.fetch(1)
162
+ # => raises Jade::Interop::DecodeError:
163
+ # Port returned a value that failed to decode at value: missing field `id` ({name: "Paul"})
164
+ ```
165
+
166
+ The Jade caller never sees a malformed `User` — the bug is caught at the entry
167
+ point, and the error arm (here `String`) stays meaningful for real failures.
168
+
169
+ ## What the compiled boundary looks like
170
+
171
+ For a function with a primitive argument:
172
+
173
+ ```jade
174
+ module Sample exposing (absolute)
175
+
176
+ def absolute(n: Int) -> Int
177
+ n < 0 ? 0 - n : n
178
+ end
179
+ ```
180
+
181
+ the compiler emits:
182
+
183
+ ```ruby
184
+ module Sample
185
+ extend self
186
+
187
+ module Internal
188
+ extend self
189
+
190
+ def absolute(n)
191
+ if ((n < 0))
192
+ (0 - n)
193
+ else
194
+ n
195
+ end
196
+ end
197
+ end
198
+
199
+ def self.absolute(n)
200
+ Internal.absolute(Jade::Interop::Boundary.integer("Int", n))
201
+ end
202
+ end
203
+ ```
204
+
205
+ Two surface methods — `Internal.absolute` (pure) and `self.absolute` (the
206
+ boundary). `Int` has a specialized fast-path coercion; richer types decode
207
+ through cached `Decode` constants instead. Either way the boundary work is
208
+ visible in the file, not hidden inside a runtime hook.
data/docs/json.md ADDED
@@ -0,0 +1,163 @@
1
+ # Decoding and encoding
2
+
3
+ `Decode` turns untyped JSON / Ruby data into typed Jade values; `Encode` turns
4
+ typed values back into JSON. Both are explicit pipelines, and both auto-derive
5
+ for `struct` types so the common case needs no hand-written decoder or encoder.
6
+
7
+ ## Decoding
8
+
9
+ A decoder is a value; `Decode.decode_string(decoder, json)` runs one and returns
10
+ a `Result(a, DecodeError)`. The combinators compose:
11
+
12
+ ```jade
13
+ module DecodeJson exposing (
14
+ age,
15
+ coords,
16
+ tags,
17
+ user,
18
+ )
19
+
20
+ import Decode exposing (DecodeError)
21
+
22
+
23
+ struct User = {
24
+ name: String,
25
+ age: Int
26
+ }
27
+
28
+
29
+ def age(json: String) -> Result(Int, DecodeError)
30
+ Decode.decode_string(Decode.field("age", Decode.int), json)
31
+ end
32
+
33
+
34
+ def tags(json: String) -> Result(List(String), DecodeError)
35
+ Decode.decode_string(Decode.list(Decode.string), json)
36
+ end
37
+
38
+
39
+ def coords(json: String) -> Result(Maybe(Int), DecodeError)
40
+ Decode.decode_string(Decode.nullable(Decode.int), json)
41
+ end
42
+
43
+
44
+ def user(json: String) -> Result(User, DecodeError)
45
+ decoder = Decode.succeed(User(_, _))
46
+ |> Decode.required("name", Decode.string)
47
+ |> Decode.required("age", Decode.int)
48
+
49
+ Decode.decode_string(decoder, json)
50
+ end
51
+ ```
52
+
53
+ ```ruby
54
+ DecodeJson::Internal.age('{"age":40}') # => Ok(40)
55
+ DecodeJson::Internal.tags('["a","b"]') # => Ok(["a", "b"])
56
+ DecodeJson::Internal.coords('null') # => Ok(Nothing)
57
+ DecodeJson::Internal.coords('7') # => Ok(Just(7))
58
+ DecodeJson::Internal.user('{"name":"Ada","age":40}')
59
+ # => Ok(User(name: "Ada", age: 40))
60
+ DecodeJson::Internal.user('{"name":"Ada"}')
61
+ # => Err(MissingField("age"))
62
+ ```
63
+
64
+ The struct decoder is `Decode.succeed(User(_, _))` piped through one
65
+ `Decode.required` per field — the `_` placeholders are the constructor's holes,
66
+ filled left to right as each field decodes.
67
+
68
+ ## Encoding
69
+
70
+ `Encode.encode_to_string(value)` serializes; the combinators mirror `Decode`:
71
+
72
+ ```jade
73
+ module EncodeJson exposing (n, point, user, xs)
74
+
75
+ import Encode
76
+
77
+
78
+ struct User = {
79
+ name: String,
80
+ age: Int
81
+ }
82
+
83
+
84
+ def n -> String
85
+ Encode.encode_to_string(Encode.int(42))
86
+ end
87
+
88
+
89
+ def xs -> String
90
+ Encode.encode_to_string(Encode.list(Encode.int, [1, 2, 3]))
91
+ end
92
+
93
+
94
+ def point -> String
95
+ pairs = [
96
+ Encode.field("x", Encode.int, 1),
97
+ Encode.field("y", Encode.int, 2),
98
+ ]
99
+
100
+ Encode.encode_to_string(Encode.object(pairs))
101
+ end
102
+
103
+
104
+ def user(u: User) -> String
105
+ Encode.encode_to_string(Encode.encode(u))
106
+ end
107
+ ```
108
+
109
+ ```ruby
110
+ EncodeJson.n # => "42"
111
+ EncodeJson.xs # => "[1,2,3]"
112
+ EncodeJson.point # => '{"x":1,"y":2}'
113
+ EncodeJson::Internal.user(user)
114
+ # => '{"name":"Ada","age":40}'
115
+ ```
116
+
117
+ ## Auto-derivation
118
+
119
+ `Encode.encode(value)` derives the encoder from the value's type, and
120
+ `Decode.from_json(json)` derives the decoder from the **return type** — so a
121
+ struct round-trips without writing either by hand:
122
+
123
+ ```jade
124
+ module Api exposing (parse, render)
125
+
126
+ import Encode
127
+ import Decode exposing (DecodeError)
128
+
129
+
130
+ struct User = {
131
+ name: String,
132
+ age: Int
133
+ }
134
+
135
+
136
+ def parse(json: String) -> Result(User, DecodeError)
137
+ Decode.from_json(json)
138
+ end
139
+
140
+
141
+ def render(user: User) -> String
142
+ Encode.encode_to_string(Encode.encode(user))
143
+ end
144
+ ```
145
+
146
+ Reach for the explicit combinators above when the JSON shape doesn't match the
147
+ struct one-to-one — renamed keys, nested lookups, optional fields.
148
+
149
+ Derivation reaches through the structural types to their elements, so anything
150
+ built out of encodable parts is itself encodable:
151
+
152
+ | Type | Wire form |
153
+ |------|-----------|
154
+ | `List(a)`, `Set(a)` | array — a set drops duplicates on the way back |
155
+ | `Maybe(a)` | the value, or `null` |
156
+ | `(a, b)` … `(a, b, c, d)` | array, positional |
157
+ | `Dict(k, v)` | array of `[key, value]` pairs — a JSON object only admits string keys |
158
+ | a `struct` | object, keyed by field name |
159
+ | a union whose variants take no arguments | string, the variant name in snake_case |
160
+
161
+ A type outside that list needs its own `implements Encodable(T)` /
162
+ `Decodable(T)` — a union carrying arguments, say, where nothing but you knows
163
+ which shape it should take.
data/docs/lsp.md ADDED
@@ -0,0 +1,105 @@
1
+ # Language server
2
+
3
+ Jade ships a language server, run as `jade lsp`. It speaks LSP over stdio and
4
+ works in any editor with an LSP client.
5
+
6
+ ## What it does
7
+
8
+ - **Diagnostics.** Type and parse errors are streamed to the editor as you type.
9
+ - **Hover.** Hover any name, local, or expression to see its inferred type.
10
+ - **Go to definition.** Jump from a use site to where a name is declared —
11
+ across modules, into the stdlib, and to interface implementations.
12
+ - **Find references.** Every use of a name across the project, declaration
13
+ included.
14
+ - **Document symbols.** The editor's outline ("show all functions in this
15
+ file") works.
16
+
17
+ ## Editor setup
18
+
19
+ The server is `jade lsp` on stdio. Point your editor's LSP client at it for
20
+ files matching `*.jd`.
21
+
22
+ ### Neovim
23
+
24
+ ```lua
25
+ -- Treat *.jd files as filetype "jd"
26
+ vim.filetype.add({ extension = { jd = 'jd' } })
27
+
28
+ vim.lsp.start({
29
+ name = 'jade-lsp',
30
+ cmd = { 'jade', 'lsp' },
31
+ root_dir = vim.fs.dirname(vim.fs.find({ 'Gemfile', '.git' }, { upward = true })[1]),
32
+ filetypes = { 'jd' },
33
+ })
34
+ ```
35
+
36
+ ### VS Code
37
+
38
+ No published extension yet. The server works with any LSP-client extension that
39
+ lets you point at a custom command (e.g. `vscode-languageclient` with a small
40
+ wrapper that runs `jade lsp`).
41
+
42
+ ### Other editors
43
+
44
+ If your editor speaks LSP, it'll work — the server depends on no editor-specific
45
+ bridge.
46
+
47
+ ## For agents that don't speak LSP
48
+
49
+ `jade q` exposes the same compiler intelligence as one-shot JSON over stdout —
50
+ handy for agents and scripts that don't want to manage a JSON-RPC session:
51
+
52
+ ```
53
+ jade q hover file.jd:LINE:COL # type info at a position
54
+ jade q defn file.jd:LINE:COL # goto-definition target
55
+ jade q refs file.jd:LINE:COL # all references (incl. declaration)
56
+ jade q symbols file.jd # document outline
57
+ jade q api [MODULE|NAME] # signatures, by module or symbol
58
+ jade q find TERM # every symbol matching TERM
59
+ jade q syntax [FORM] # how a form is written
60
+ ```
61
+
62
+ `LINE` and `COL` are 0-indexed (LSP convention). Paths are relative to the
63
+ project root; compile results are cached at `.jade/cache`, so repeat queries are
64
+ fast.
65
+
66
+ `api` and `find` cover the stdlib, the project's own modules, and any extension
67
+ gem's — each tagged with its `origin`. Without a `jade.json` they fall back to
68
+ the stdlib alone, so they still answer from any directory. See
69
+ [stdlib.md](stdlib.md#finding-a-function).
70
+
71
+ `syntax` answers the other half. A signature says a function exists and what it
72
+ takes; it never says how a lambda or an `implements` block is written:
73
+
74
+ ```
75
+ $ jade q syntax lambda
76
+ { "form": "lambda", "detail": "anonymous function", "source": "(args) -> { body }" }
77
+ ```
78
+
79
+ The forms are `def`, `type`, `struct`, `case`, `if`, `module`, `import`,
80
+ `interface`, `implements`, `uses`, `lambda` — the same corpus the editor
81
+ offers as completions, so the two can't drift. They're templates, not
82
+ compilable programs. It reads no files and needs no project.
83
+
84
+ ## Checking without an editor
85
+
86
+ `jade check` type-checks and prints diagnostics, generating nothing:
87
+
88
+ ```
89
+ jade check # every .jd under the source root
90
+ jade check lib/orders.jd # that file and everything it imports
91
+ ```
92
+
93
+ Exit 1 if there were errors, 0 otherwise. This is the loop to close after
94
+ editing a file — it's the same front end the LSP runs, so an invented function
95
+ or a wrong argument order surfaces immediately instead of at the next build.
96
+
97
+ ## What it doesn't do yet
98
+
99
+ - Autocomplete
100
+ - Refactor / rename
101
+ - Code actions (e.g. "add the missing case to this `case`")
102
+ - Workspace-wide symbol search
103
+
104
+ These are the natural next features; the current server is the minimum useful
105
+ surface.
data/docs/stdlib.md ADDED
@@ -0,0 +1,69 @@
1
+ # Standard library
2
+
3
+ ## Finding a function
4
+
5
+ Ask the compiler, don't grep:
6
+
7
+ ```
8
+ jade q api # every module, and where it came from
9
+ jade q api Dict # one module, with signatures
10
+ jade q api List.fold # one symbol
11
+ jade q find fold # everything named `fold`
12
+ ```
13
+
14
+ Grep is the wrong tool here, and quietly so. Stdlib modules are written two
15
+ ways — Jade source in a heredoc (`Maybe`, `Result`, `Decimal`, …) and a Ruby
16
+ DSL (`List`, `String`, `Dict`, `Decode`, …) — so half of them never spell `def`
17
+ at all, and none of them contains the string `List.map` you'd search for.
18
+ Extension modules aren't in your tree at all; they live in a gem. A grep that
19
+ comes back empty means nothing.
20
+
21
+ `jade q api` reads the registry the compiler itself resolves against, so it
22
+ can't drift from what exists. It answers what grep can't: the argument order
23
+ (`List.fold` folds with `(b, a) -> b`, `Dict.fold` with `(k, v, b) -> b`), the
24
+ interface constraints (`List.sort : Comparable a => …`), a struct's fields
25
+ (`struct Date = { year : Int, month : Month, day : Int }`), and which types
26
+ implement `Decodable` or `Encodable`.
27
+
28
+ Run inside a project and it covers three origins, each tagged in the output:
29
+
30
+ | `origin` | what |
31
+ |---|---|
32
+ | `stdlib` | the modules below |
33
+ | `project` | your own `.jd` files, under the source root |
34
+ | `extension` | modules an extension gem ships (`jade-sql`'s `Sql.Query`, …) |
35
+
36
+ Without a `jade.json` it falls back to the stdlib alone, so the query still
37
+ works from anywhere. A file that won't compile is named under `skipped` rather
38
+ than quietly missing — an absent module reads as "that function doesn't
39
+ exist", which is the failure this is here to prevent.
40
+
41
+ ## What's where
42
+
43
+ Every module's source lives in [`lib/jade/stdlib/`](../lib/jade/stdlib/) — short,
44
+ readable Ruby. This is a map of the territory; `jade q api` is the atlas.
45
+
46
+ | Module | What's there |
47
+ |--------|--------------|
48
+ | `Basics` | The built-in interfaces — `Eq`, `Comparable`, `Appendable`, `Mappable`, `Chainable` — plus the `Ordering` type (`LT` / `EQ` / `GT`) and `Never`. `++` works on `String`, `List`, and `Bytes` via `Appendable`. |
49
+ | `Maybe` | Optional values without `nil`: `Just(a)` / `Nothing`, with `map`, `and_then`, `with_default`. |
50
+ | `Result` | Errors as values, no exceptions: `Ok(a)` / `Err(e)`, with `map`, `and_then`, `map_error`, `on_error`, `sequence`. |
51
+ | `List` | Immutable lists: `map`, `filter`, `fold`, `zip`, `sort`, `length`, `range`, `head`, `tail`, `take`, `drop`, … |
52
+ | `String` | Text: `length`, `reverse`, `split`, `trim`, `to_int`, `contains?`, `uncons`, `cons`, `from_char`, `map`. |
53
+ | `Char` | Character predicates and codes: `to_code`, `from_code`, `digit?`, `alpha?`, `alpha_numeric?`, `upper?`, `lower?`. |
54
+ | `Tuple` | Pair accessors: `first`, `second`, `pair`. |
55
+ | `Task` | Side-effecting actions: `succeed`, `fail`, `map`, `and_then`, `on_error`, `sequence`. See [interop.md](interop.md) for how Tasks cross the Ruby boundary. |
56
+ | `Decode` | Parse JSON / Ruby data into typed values: `string`, `int`, `float`, `bool`, `list`, `field`, `index`, `succeed`, `required`, `optional`, `nullable`, `map`, `and_then`. Auto-derived for `struct` types. See [json.md](json.md). |
57
+ | `Decode.Params` | PATCH-style decoders: only fields present in the input appear in the output, missing fields don't error. For partial-update endpoints. |
58
+ | `Encode` | Symmetric to `Decode`: `string`, `int`, `float`, `bool`, `list`, `object`. Auto-derived for `struct` types via `Encodable`. |
59
+ | `Dict` | Immutable key-value map with structural equality: `empty`, `get`, `member?`, `insert`, `update`, `remove`, `keys`, `values`, `to_list`, `from_list`, `map`, `filter`, `fold`, `union`, `merge`. |
60
+ | `Set` | Immutable set: `empty`, `insert`, `remove`, `member?`, `to_list`, `from_list`, `map`, `filter`, `fold`, `union`, `intersect`, `diff`. |
61
+ | `Bytes` | Opaque byte buffer: `empty`, `width`, `from_list` / `to_list`, `from_string` / `to_string`. Implements `Eq` and `Appendable`. |
62
+ | `Calendar` | Dates and date arithmetic: `Date`, `today`. Days, months, years; no time of day — use `Clock`. |
63
+ | `Clock` | Timestamps and monotonic timing: `Instant`, `now`. Sub-second precision; the bridge to wall-clock time. |
64
+ | `Show` | Renders a value the way Jade writes it: `show(Just(7))` is `"Just(7)"`, `show(Point(3, 4))` is `"Point { x: 3, y: 4 }"`. Instances for the primitives; derives for unions, structs, records and lists. A function shows as `<function>`, and `Never` raises — it has no values. |
65
+ | `Debug` | `log(label, value)` prints `label: value` to stderr and returns the value untouched, so it drops into a pipeline. Unconstrained, unlike `Show`. |
66
+ | `Decimal` | Exact base-10 decimals (`coefficient * 10 ^ exponent`) — money and rates without `Float` rounding. Opaque; build with `of` / `scaled` / `parse`. Arithmetic via `Numeric` (`+` `-` `*` `/`), plus `div` (scaled, half-up), `round`, `to_i`, `to_float`. JSON-encodes to a `<mantissa>e<exponent>` string. |
67
+
68
+ Stdlib operations compile inline rather than through a runtime dispatch layer,
69
+ so the generated Ruby calls the underlying operation directly.