catalogue.json (39532B)
1 [ 2 { 3 "id": "names-values", 4 "title": "Names and values", 5 "category": "Basic Examples", 6 "hash": "4116a3702923898d5fafc8c855fbea38a7fd740bdb15667ef53fec7268b41381", 7 "source": "!export *\n\n-- Welcome to tricu! Anything after -- on a line is a comment.\n-- tricu is a lazy functional language based on tree calculus.\n\n-- Click or tap underlined definition names to inspect their values.\n-- Function calls can have names too; we'll try that in \"Your first function\".\n-- Edits compile after a pause. IO programs offer \"Run IO\" inside their card.\n\n-- tricu offers string literals,\ngreeting = \"Hello, world!\"\n\n-- natural number literals (whole numbers starting at zero),\nanswer = 42\n\n-- list literals,\ngreetAndAnswer = [greeting answer]\n\n-- and more! Choose \"Doing arithmetic\" from the \"Program\" dropdown next.\n", 8 "sourceSha256": "f91426877f7cbd74aa4de3f8241844e38bd724ed1c3766ee50e04f548a6a31fd", 9 "export": "greeting", 10 "arguments": [], 11 "output": "string", 12 "mode": "pure", 13 "presets": { 14 "greeting": { 15 "arguments": [], 16 "output": "string", 17 "mode": "pure" 18 }, 19 "answer": { 20 "arguments": [], 21 "output": "number", 22 "mode": "pure" 23 }, 24 "greetAndAnswer": { 25 "arguments": [], 26 "output": "values", 27 "mode": "pure" 28 } 29 }, 30 "environment": "empty" 31 }, 32 { 33 "id": "arithmetic", 34 "title": "Doing arithmetic", 35 "category": "Basic Examples", 36 "hash": "ac504497f656b84b7ae1e7f9a00086c76d05686df3eee2c5612a8c7f51fbf141", 37 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!export *\n\n-- Let's do some arithmetic! Our import supplies add and mul (multiply).\n-- Function calls use spaces between the function and its inputs.\ntotal = add 5 7\nmultiplied = mul 5 7\n\n-- Parentheses make one calculation the input to another.\ngrouped = mul (add 5 7) 2\n\n-- Try changing the numbers to calculate the cost of two things you'd buy.\n-- Next: Your first function.\n", 38 "sourceSha256": "de1aea17cb2cab0a386f491f1e08fe00aec48c80967740ee55756987c69d328d", 39 "export": "total", 40 "arguments": [], 41 "output": "number", 42 "mode": "pure", 43 "presets": { 44 "total": { 45 "arguments": [], 46 "output": "number", 47 "mode": "pure" 48 }, 49 "multiplied": { 50 "arguments": [], 51 "output": "number", 52 "mode": "pure" 53 }, 54 "grouped": { 55 "arguments": [], 56 "output": "number", 57 "mode": "pure" 58 } 59 }, 60 "environment": "empty" 61 }, 62 { 63 "id": "functions", 64 "title": "Your first function", 65 "category": "Basic Examples", 66 "hash": "6c85d85a51c0c3ed8fb9adc1a5c81c18313174b3f72371933e432f3f895c1667", 67 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!export *\n\n-- Give a calculation a name so we can reuse it with different inputs.\ndouble number = mul number 2\n\n-- number is a parameter: each call supplies its value.\nsixDoubled = double 6\nnineDoubled = double 9\n\n-- A function can take more than one input.\nrectangle width height = mul width height\narea = rectangle 4 7\n\n-- Try writing triple and using it in place of double.\n-- Next: Working with lists.\n", 68 "sourceSha256": "3e07457439457ad90533a9e1ecee410d3648d001b7308c6e8aaf41f03644e2f7", 69 "export": "sixDoubled", 70 "arguments": [], 71 "output": "number", 72 "mode": "pure", 73 "presets": { 74 "sixDoubled": { 75 "arguments": [], 76 "output": "number", 77 "mode": "pure" 78 }, 79 "nineDoubled": { 80 "arguments": [], 81 "output": "number", 82 "mode": "pure" 83 }, 84 "double": { 85 "arguments": [ 86 { 87 "type": "number", 88 "value": "6" 89 } 90 ], 91 "output": "number", 92 "mode": "pure" 93 }, 94 "rectangle": { 95 "arguments": [ 96 { 97 "type": "number", 98 "value": "4" 99 }, 100 { 101 "type": "number", 102 "value": "7" 103 } 104 ], 105 "output": "number", 106 "mode": "pure" 107 }, 108 "area": { 109 "arguments": [], 110 "output": "number", 111 "mode": "pure" 112 } 113 }, 114 "environment": "empty" 115 }, 116 { 117 "id": "lists", 118 "title": "Working with lists", 119 "category": "Basic Examples", 120 "hash": "8eb72ff6766fb762614c6dca9026ebda78d06f5e8f78e48cc29b195ba2154b21", 121 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!export *\n\n-- Collect values in a list. Spaces separate items; [] is the empty list.\nnumbers = [2 4 6]\nitemCount = length numbers\ntotal = sum numbers\n\n-- Try adding another item to numbers, then try numbers = [].\n-- Next: Transforming a list.\n", 122 "sourceSha256": "ba33f8097d479eeb3adcbcc92c7b33931970df2b7ea4d523535673a095e5a1b2", 123 "export": "itemCount", 124 "arguments": [], 125 "output": "number", 126 "mode": "pure", 127 "presets": { 128 "itemCount": { 129 "arguments": [], 130 "output": "number", 131 "mode": "pure" 132 }, 133 "total": { 134 "arguments": [], 135 "output": "number", 136 "mode": "pure" 137 }, 138 "numbers": { 139 "arguments": [], 140 "output": "values", 141 "mode": "pure" 142 } 143 }, 144 "environment": "empty" 145 }, 146 { 147 "id": "transform-list", 148 "title": "Transforming a list", 149 "category": "Basic Examples", 150 "hash": "639c8d3959320ef99d9416257905f189519a90ea4b315cdbb0485a386e46191c", 151 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!export *\n\n-- Functions are values too. We can pass one to another function.\ndouble number = mul number 2\nnumbers = [2 4 6]\n\n-- map takes a function, then a list, and applies it to every item.\ndoubled = map double numbers\n-- map builds a new list; the original numbers are unchanged.\ntotal = sum doubled\n\n-- Try multiplying by 3 instead.\n-- Next: Making a choice.\n", 152 "sourceSha256": "e752330fcedfc61065c695ebbafa1e6bc65745d890ee98c7682d4e5fe11bc45f", 153 "export": "total", 154 "arguments": [], 155 "output": "number", 156 "mode": "pure", 157 "presets": { 158 "total": { 159 "arguments": [], 160 "output": "number", 161 "mode": "pure" 162 }, 163 "double": { 164 "arguments": [ 165 { 166 "type": "number", 167 "value": "5" 168 } 169 ], 170 "output": "number", 171 "mode": "pure" 172 }, 173 "numbers": { 174 "arguments": [], 175 "output": "values", 176 "mode": "pure" 177 }, 178 "doubled": { 179 "arguments": [], 180 "output": "values", 181 "mode": "pure" 182 } 183 }, 184 "environment": "empty" 185 }, 186 { 187 "id": "choices", 188 "title": "Making a choice", 189 "category": "Basic Examples", 190 "hash": "dd0931401769303bf2e1ce08c51e7778779ccc9643ca8315c256c0542eedff7a", 191 "source": "!export *\n\n-- Choose a message by matching an input.\nmessage 0 = \"Your basket is empty.\"\nmessage 1 = \"You have one item.\"\nmessage _ = \"You have several items.\"\n\n-- These equations are tried from top to bottom.\n-- _ matches any remaining input: this is the fallback.\nemptyBasket = message 0\noneItem = message 1\nfullBasket = message 4\n\n-- Try adding a message for 2 items before the fallback equation.\n-- Keep the equations together so they define one function.\n-- Next: a Practical Example, or Language reference.\n", 192 "sourceSha256": "b3e3cbfdf958715f14dca4bfd0b69e58a6e1b250ee080cbddfa29b0711e0961d", 193 "export": "emptyBasket", 194 "arguments": [], 195 "output": "string", 196 "mode": "pure", 197 "presets": { 198 "emptyBasket": { 199 "arguments": [], 200 "output": "string", 201 "mode": "pure" 202 }, 203 "oneItem": { 204 "arguments": [], 205 "output": "string", 206 "mode": "pure" 207 }, 208 "fullBasket": { 209 "arguments": [], 210 "output": "string", 211 "mode": "pure" 212 }, 213 "message": { 214 "arguments": [ 215 { 216 "type": "number", 217 "value": "0" 218 } 219 ], 220 "output": "string", 221 "mode": "pure" 222 } 223 }, 224 "environment": "empty" 225 }, 226 { 227 "id": "language-reference", 228 "title": "Language reference", 229 "category": "Language & Runtime Explorations", 230 "hash": "86717d7f9d69fe730c6c6172c42c49895623aa070bf675e1b5f751adb30616c1", 231 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 7f87549d587fa8c3be88af0ea988db3adcc3b35f1ffb48973553491b075614db as Math\n!export *\n\n-- The open base import supplies contract helpers and unqualified names.\n-- Math is a qualified import: its names start with Math.\n-- A syntax sampler: independent examples, rather than one application.\n-- Start with answer: its value is 42.\n-- Full contracts: follow For AI assistants in the footer to Language features.\n-- Comments give sample calls and interpretations; name a call to try it.\n\n-- Names, lambdas, named parameters, application and grouping.\nidentity = (x : x)\nfirst a b = a\nanswer = identity (first 42 7)\n-- identity \"hello\" / Text -> hello; first 7 9 / Natural number -> 7.\n\n-- The kernel constructors: Leaf, Stem and Fork. Use tricu for arbitrary trees.\nleaf = t\nshapes = [t (t t) (t t t)]\n-- leaf / tricu -> t; shapes / tricu shows a list containing all three shapes.\nliterals = [\"hello\" 42 [1 2 3]]\n-- literals / tricu shows mixed values. Strings support Unicode; names are ASCII.\n-- Lists use spaces. Group application items: [(identity 1) (identity 2)].\n\n-- Partial application: provide one of a function's arguments now.\naddTwo = Math.add 2\n-- addTwo 5 / Natural number -> 7.\n\n-- Ordered equations: literals, a pin, exact lists, cons and wildcard.\n-- A pin (^true) compares against an existing value.\ndescribe 7 = \"seven\"\ndescribe \"hi\" = \"greeting\"\ndescribe ^true = \"true\"\ndescribe [a b] = \"two items\"\ndescribe [head | tail] = \"nonempty list\"\ndescribe [] = \"empty list\"\ndescribe _ = \"other\"\n-- Try each with Text: describe 7, describe \"hi\", describe (t t),\n-- describe [4 5], describe [1 2 3], describe [], describe (t (t t)).\n\n-- Pair patterns and whole-value capture. Construct a pair with t left right.\nkeep whole{left, right} = whole\nkeep _ = t\n-- keep (t t t) / tricu -> (t t t); keep (t t) / tricu -> t.\n\n-- Plain local bindings are sequential and nonrecursive.\nlocal x = let\n double n = Math.multiply n 2\n in double x\nviaWhere x = step x where step y = Math.add y 1\n-- local 6 -> 12; viaWhere 6 -> 7, both Natural number.\n\n-- Direct self-recursion, with a final fallback equation.\ncopy [] = []\ncopy [head | tail] = t head <| copy tail\ncopy _ = []\n-- copy [1 2 3] / Bytes -> [1, 2, 3]. Bytes displays naturals from 0 through 255.\n-- Use tricu for a general list; copy [] and copy (t t) both return [].\n\n-- case examines an intermediate value; arms align and finish with a fallback.\nfirstOr fallback xs = case xs of\n [head | _] = head\n _ = fallback\n-- firstOr 99 [5] -> 5; firstOr 99 [] -> 99, both Natural number.\n\n-- Local self-recursion is explicit: let rec (also available with where rec).\nlocalCopy xs = let rec\n walk [] = []\n walk [head | tail] = t head <| walk tail\n walk _ = []\n in walk xs\n-- localCopy [4 5] / Bytes -> [4, 5]; localCopy [] -> [].\n\n-- Parameter and result contracts check values at runtime.\nchecked x@nat? =@nat? x\n-- checked 42 / Natural number -> 42. A failed contract produces a Result.\n-- Keep annotations on plain function heads; see Contracts for Result handlers.\n\n-- Pipes and do use an explicit sequencing operator. Here a lambda passes a value\n-- to the next continuation. It is ordinary pure code; IO uses IO.bind instead.\nsequenced input = do (value next : next value)\n value <- input |> identity\n identity value\n identity <| value\n-- sequenced 9 / Natural number -> 9.\n-- Both pipe chains associate left-to-right; group nested applications explicitly.\n\n|- Block comments are another way to annotate source. They do not nest. -|\n-- !export * selects local definitions. Use !export name for a smaller interface.\n-- An open import uses !import MANIFEST *; a reexport uses !export * from MANIFEST.\n-- Replace MANIFEST with the full identity from Explore libraries or the library guide.\n-- Reexports select the public interface; imports introduce names into scope.\n-- See Conversation for IO, and the docs for Maybe/Result/parser protocols.\n", 232 "sourceSha256": "d464c7964012c904cc322e957990be26c4172e11eea745ef5f75d4cd03a6635f", 233 "export": "answer", 234 "arguments": [], 235 "output": "number", 236 "mode": "pure", 237 "presets": { 238 "answer": { 239 "arguments": [], 240 "output": "number", 241 "mode": "pure" 242 }, 243 "identity": { 244 "arguments": [ 245 { 246 "type": "number", 247 "value": "7" 248 } 249 ], 250 "output": "number", 251 "mode": "pure" 252 }, 253 "first": { 254 "arguments": [ 255 { 256 "type": "number", 257 "value": "7" 258 }, 259 { 260 "type": "number", 261 "value": "9" 262 } 263 ], 264 "output": "number", 265 "mode": "pure" 266 }, 267 "leaf": { 268 "arguments": [], 269 "output": "tricu", 270 "mode": "pure" 271 }, 272 "shapes": { 273 "arguments": [], 274 "output": "tricu", 275 "mode": "pure" 276 }, 277 "literals": { 278 "arguments": [], 279 "output": "tricu", 280 "mode": "pure" 281 }, 282 "addTwo": { 283 "arguments": [ 284 { 285 "type": "number", 286 "value": "5" 287 } 288 ], 289 "output": "number", 290 "mode": "pure" 291 }, 292 "describe": { 293 "arguments": [ 294 { 295 "type": "number", 296 "value": "7" 297 } 298 ], 299 "output": "string", 300 "mode": "pure" 301 }, 302 "keep": { 303 "arguments": [ 304 { 305 "type": "tree", 306 "value": "200" 307 } 308 ], 309 "output": "tricu", 310 "mode": "pure" 311 }, 312 "local": { 313 "arguments": [ 314 { 315 "type": "number", 316 "value": "6" 317 } 318 ], 319 "output": "number", 320 "mode": "pure" 321 }, 322 "viaWhere": { 323 "arguments": [ 324 { 325 "type": "number", 326 "value": "6" 327 } 328 ], 329 "output": "number", 330 "mode": "pure" 331 }, 332 "copy": { 333 "arguments": [ 334 { 335 "type": "list", 336 "value": [ 337 { 338 "type": "number", 339 "value": "1" 340 }, 341 { 342 "type": "number", 343 "value": "2" 344 }, 345 { 346 "type": "number", 347 "value": "3" 348 } 349 ] 350 } 351 ], 352 "output": "bytes", 353 "mode": "pure" 354 }, 355 "firstOr": { 356 "arguments": [ 357 { 358 "type": "number", 359 "value": "99" 360 }, 361 { 362 "type": "list", 363 "value": [ 364 { 365 "type": "number", 366 "value": "5" 367 } 368 ] 369 } 370 ], 371 "output": "number", 372 "mode": "pure" 373 }, 374 "localCopy": { 375 "arguments": [ 376 { 377 "type": "list", 378 "value": [ 379 { 380 "type": "number", 381 "value": "4" 382 }, 383 { 384 "type": "number", 385 "value": "5" 386 } 387 ] 388 } 389 ], 390 "output": "bytes", 391 "mode": "pure" 392 }, 393 "checked": { 394 "arguments": [ 395 { 396 "type": "number", 397 "value": "42" 398 } 399 ], 400 "output": "number", 401 "mode": "pure" 402 }, 403 "sequenced": { 404 "arguments": [ 405 { 406 "type": "number", 407 "value": "9" 408 } 409 ], 410 "output": "number", 411 "mode": "pure" 412 } 413 }, 414 "environment": "empty" 415 }, 416 { 417 "id": "greet", 418 "title": "A conversation", 419 "category": "Practical Examples", 420 "hash": "b6f32449d85257fbd0a589d29ef05ea4a503c4a7e6105926d408b0551a34fd77", 421 "source": "!import 3dbb76ee4b350c161ed4f9c9f600034195b2fcf99202d46b8adcd25715a56f6b as IO\n!import 11791107bece9dac78ff92dcec75d11830921e359976f561a934b36f9e8910df as Text\n!export main\n\n-- A conversation: ask for a name, then greet the person who replies.\n-- Send a line or choose \"End input\" in the card's terminal.\n-- Read and write Results are checked before continuing.\n-- Return the name when IO finishes. Try changing the greeting.\ngreet writer [[\"line\" name _] _] = do IO.require\n Text.writeText writer \"Hello, \"\n Text.writeText writer name\n Text.writeText writer \"!\\n\"\n IO.pure name\ngreet writer [[\"eof\"] _] = IO.require\n (Text.writeText writer \"End of input.\\n\") (_ : IO.pure \"\")\ngreet _ _ = IO.fail \"Invalid line reply\"\n\nmain = IO.io\n do IO.require\n reader <- IO.stdin\n writer <- IO.stdout\n Text.writeText writer \"What is your name?\\n\"\n line <- Text.readLine (Text.lineCursor reader) 4096\n greet writer line\n", 422 "sourceSha256": "269f5ee9dcfa6cada4d18597fb35b2a1354535f204d38e3aab7d03621ad7adad", 423 "export": "main", 424 "arguments": [], 425 "output": "string", 426 "mode": "io", 427 "presets": {}, 428 "environment": "empty" 429 }, 430 { 431 "id": "microscope", 432 "title": "Tree microscope", 433 "category": "Language & Runtime Explorations", 434 "hash": "493a125b649639c0df8348b4aeb3a455b7139cae0634ba114866c48813500bc5", 435 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!export shape\n!export size\n!export same\n\n-- Inspect data OR compiled program trees, not source text.\n-- Try shape (t t t), size (t t t), or same (t t) (t t).\n-- Also try shape (x : x): functions are inspectable trees too.\n-- triage dispatches directly on Leaf, Stem and Fork without visiting children.\n-- Arithmetic and Booleans come from base; traversal is the subject of this demo.\nshape tree = triage \"Leaf\" (child : \"Stem\") (left right : \"Fork\") tree\n\n-- Count every constructor occurrence, including repeated shared branches.\n-- This is expanded tree size, not allocated nodes or source-code length.\ncountNodes tree acc = triage\n (succ acc)\n (child : countNodes child <| succ acc)\n (left right : succ acc |> countNodes left |> countNodes right)\n tree\n\nsize tree = countNodes tree 0\n\nsame a b = triage\n (triage true (ignored : false) (ignored ignored2 : false) b)\n (x : triage false (y : same x y) (ignored ignored2 : false) b)\n (x y : triage false (ignored : false) (u v : and? (same x u) (same y v)) b)\n a\n", 436 "sourceSha256": "ac4bd61b5953c523e29a42387acfc6305075066edf6c709283b02de19c51025c", 437 "export": "size", 438 "arguments": [ 439 { 440 "type": "tree", 441 "value": "21100" 442 } 443 ], 444 "output": "number", 445 "mode": "pure", 446 "presets": { 447 "shape": { 448 "arguments": [ 449 { 450 "type": "tree", 451 "value": "21100" 452 } 453 ], 454 "output": "string", 455 "mode": "pure" 456 }, 457 "size": { 458 "arguments": [ 459 { 460 "type": "tree", 461 "value": "21100" 462 } 463 ], 464 "output": "number", 465 "mode": "pure" 466 }, 467 "same": { 468 "arguments": [ 469 { 470 "type": "tree", 471 "value": "21100" 472 }, 473 { 474 "type": "tree", 475 "value": "21100" 476 } 477 ], 478 "output": "bool", 479 "mode": "pure" 480 } 481 }, 482 "environment": "empty" 483 }, 484 { 485 "id": "rgb", 486 "title": "RGB validator", 487 "category": "Practical Examples", 488 "hash": "b8c0e21c09f63511794cc62827a6b231e62a55f7e779022a1b740aaba29b0368", 489 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 7a24af56a9ce9b947cadcafd3a9da127b1507275bfac62b9fa55e27490b176dc *\n!export main\n\n-- Validate an RGB list using the library's canonical-byte predicate.\n-- Try result = main [64 128 255].\n-- Try changing its input to [0 256 255], then [0 1].\n-- Canonical encoding matters: redundant high zero bits are rejected too.\n-- List-shape errors are separate from invalid channels; checks short-circuit.\nmain (t _) = \"Expected a list.\"\nmain [_ | (t _)] = \"Expected a list.\"\nmain [_ | [_ | (t _)]] = \"Expected a list.\"\nmain [r g b] = case and? (binaryByte? r) (and? (binaryByte? g) (binaryByte? b)) of\n ^true = \"RGB triple accepted.\"\n _ = \"A channel is not a canonical byte (0-255).\"\nmain _ = \"Expected exactly three channels.\"\n", 490 "sourceSha256": "721a08c37746e7b982994a7981b8fcff7312d889dac3276e7e72ca8cc06ed87a", 491 "export": "main", 492 "arguments": [ 493 { 494 "type": "bytes", 495 "value": [ 496 64, 497 128, 498 255 499 ] 500 } 501 ], 502 "output": "string", 503 "mode": "pure", 504 "presets": {}, 505 "environment": "empty" 506 }, 507 { 508 "id": "shipping", 509 "title": "Shipping policies", 510 "category": "Practical Examples", 511 "hash": "4aa9ce9f19dfce87ecb3f6bd0a2e881dafcbcd3d2e4b4075befbb378d80833ea", 512 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!export v1\n!export v2\n\n-- Shipping policy: arguments are destination, cart subtotal in cents, grams.\n-- Destinations: domestic, regional, international. Grams: 1..5000.\n-- Try result = v1 \"domestic\" 8000 1500.\n-- Change v1 to v2: domestic free shipping changes from $75 to $100.\n-- Otherwise domestic costs $4.99/$5.99, regional $9.99, international $19.99.\n-- Packages above 1kg add $2.50. Subtotals are bounded to $10,000.\n-- The bundle hash + export pins the policy used to reproduce a quote later.\n-- Records use [fieldName tag value]: 0 = text, 1 = natural, 2 = Boolean.\n-- Base supplies canonical-natural checks, arithmetic and Booleans.\n\nfailure message = [[\"status\" 0 \"error\"] [\"message\" 0 message]]\n\nbaseRate 1 \"domestic\" = 499\nbaseRate _ \"domestic\" = 599\nbaseRate _ \"regional\" = 999\nbaseRate _ _ = 1999\n\npaidRate version zone grams = case lte_? grams 1000 of\n ^true = baseRate version zone\n _ = add 250 <| baseRate version zone\n\nfreeThreshold 1 = 7500\nfreeThreshold _ = 10000\n\namount version zone subtotal grams = case and? (equal? zone \"domestic\") (lte_? (freeThreshold version) subtotal) of\n ^true = 0\n _ = paidRate version zone grams\n\nquote version zone subtotal grams = case and? (isNat? subtotal) (isNat? grams) of\n ^true = case or? (equal? zone \"domestic\") (or? (equal? zone \"regional\") (equal? zone \"international\")) of\n ^true = case and? (lte_? subtotal 1000000) (and? (lte_? 1 grams) (lte_? grams 5000)) of\n ^true = [[\"status\" 0 \"ok\"] [\"policyVersion\" 1 version]\n [\"destination\" 0 zone] [\"shippingCents\" 1 (amount version zone subtotal grams)]]\n _ = failure \"Subtotal must be <=1000000 cents; grams must be 1..5000.\"\n _ = failure \"Unknown destination.\"\n _ = failure \"Subtotal and grams must be canonical natural numbers.\"\n\nv1 zone subtotal grams = quote 1 zone subtotal grams\nv2 zone subtotal grams = quote 2 zone subtotal grams\n", 513 "sourceSha256": "790497b064d8093290af112d8c1cc072c93da25aea4f288e87d7abefa9750e7f", 514 "export": "v1", 515 "arguments": [ 516 { 517 "type": "string", 518 "value": "domestic" 519 }, 520 { 521 "type": "number", 522 "value": "8000" 523 }, 524 { 525 "type": "number", 526 "value": "1500" 527 } 528 ], 529 "output": "tricu", 530 "mode": "pure", 531 "presets": { 532 "v1": { 533 "arguments": [ 534 { 535 "type": "string", 536 "value": "domestic" 537 }, 538 { 539 "type": "number", 540 "value": "8000" 541 }, 542 { 543 "type": "number", 544 "value": "1500" 545 } 546 ], 547 "output": "tricu", 548 "mode": "pure" 549 }, 550 "v2": { 551 "arguments": [ 552 { 553 "type": "string", 554 "value": "domestic" 555 }, 556 { 557 "type": "number", 558 "value": "8000" 559 }, 560 { 561 "type": "number", 562 "value": "1500" 563 } 564 ], 565 "output": "tricu", 566 "mode": "pure" 567 } 568 }, 569 "environment": "empty" 570 }, 571 { 572 "id": "events", 573 "title": "Event adapters", 574 "category": "Practical Examples", 575 "hash": "8b51693c63658d59c8083ec1c95cf1f6565ce5799791f540e92caf508613c9fb", 576 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 3f70d95cf1bae0928ee89acd2a9e0cdd8287796efae32a9f08d33e3dd0149c55 *\n!export vendorA\n!export vendorB\n\n-- Event adapter: two producers, one consumer-facing record schema.\n-- vendorA accepts [eventName customer totalCents]. eventName is purchase/refund.\n-- vendorB accepts [customer refunded totalCents]. refunded is a Boolean.\n-- Both return schemaVersion, eventType, customer and amountCents.\n-- Try result = vendorA [\"purchase\" \"James\" 2499].\n-- Try result = vendorB [\"James\" false 2499] to describe the same event.\n-- A producer can publish the adapter hash alongside its event schema.\n-- Records use [fieldName tag value]: 0 = text, 1 = natural, 2 = Boolean.\n-- Base supplies arithmetic/Booleans; hostString? checks Unicode scalar text.\n\nfailure message = [[\"status\" 0 \"error\"] [\"message\" 0 message]]\n\nnormalized event customer cents = case hostString? customer of\n ^true = case and? (isNat? cents) (lte_? cents 1000000) of\n ^true = [[\"schemaVersion\" 1 1] [\"eventType\" 0 event]\n [\"customer\" 0 customer] [\"amountCents\" 1 cents]]\n _ = failure \"amountCents must be a canonical natural <=1000000.\"\n _ = failure \"customer must be scalar text.\"\n\nfromA \"purchase\" customer cents = normalized \"order.created\" customer cents\nfromA \"refund\" customer cents = normalized \"order.refunded\" customer cents\nfromA _ _ _ = failure \"Unknown vendor A event.\"\n\nfromB customer ^true cents = normalized \"order.refunded\" customer cents\nfromB customer ^false cents = normalized \"order.created\" customer cents\nfromB _ _ _ = failure \"refunded must be a canonical Boolean.\"\n\n-- Preserve list-shape diagnostics without inspecting any field before dispatch.\nunpack handler (t _) = failure \"Expected a list.\"\nunpack handler [_ | (t _)] = failure \"Expected a list.\"\nunpack handler [_ | [_ | (t _)]] = failure \"Expected a list.\"\nunpack handler [a b c] = handler a b c\nunpack _ _ = failure \"Expected exactly three fields.\"\n\nvendorA values = unpack fromA values\nvendorB values = unpack fromB values\n", 577 "sourceSha256": "3d086d7b0dba582e95e28515747c6d80cd4ab0af3c825af30fd759b1788527e3", 578 "export": "vendorA", 579 "arguments": [ 580 { 581 "type": "list", 582 "value": [ 583 { 584 "type": "string", 585 "value": "purchase" 586 }, 587 { 588 "type": "string", 589 "value": "James" 590 }, 591 { 592 "type": "number", 593 "value": "2499" 594 } 595 ] 596 } 597 ], 598 "output": "tricu", 599 "mode": "pure", 600 "presets": { 601 "vendorA": { 602 "arguments": [ 603 { 604 "type": "list", 605 "value": [ 606 { 607 "type": "string", 608 "value": "purchase" 609 }, 610 { 611 "type": "string", 612 "value": "James" 613 }, 614 { 615 "type": "number", 616 "value": "2499" 617 } 618 ] 619 } 620 ], 621 "output": "tricu", 622 "mode": "pure" 623 }, 624 "vendorB": { 625 "arguments": [ 626 { 627 "type": "list", 628 "value": [ 629 { 630 "type": "string", 631 "value": "James" 632 }, 633 { 634 "type": "bool", 635 "value": false 636 }, 637 { 638 "type": "number", 639 "value": "2499" 640 } 641 ] 642 } 643 ], 644 "output": "tricu", 645 "mode": "pure" 646 } 647 }, 648 "environment": "empty" 649 }, 650 { 651 "id": "registration", 652 "title": "Registration validator", 653 "category": "Practical Examples", 654 "hash": "679733e17a86cc5e3f5282ca824cad4c9c0febd260e51f6a87e16dd14c02c45a", 655 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 3f70d95cf1bae0928ee89acd2a9e0cdd8287796efae32a9f08d33e3dd0149c55 *\n!export main\n\n-- Registration validator: name, email, age, termsAccepted are separate arguments.\n-- Returns one field-specific message per input, plus an accepted Boolean.\n-- Try result = main \"James\" \"james@example.org\" 21 true.\n-- Try changing the age to 17 or the email to \"bad\" to see field messages.\n-- Name must be nonempty scalar text. Age must be a natural from 18 to 120.\n-- Email uses a deliberately small rule: nonempty text containing @ and a dot.\n-- Terms must be the canonical true Boolean. This is a teaching policy, not SMTP validation.\n-- Browser and server can pin this same executable instead of duplicating rules.\n-- Records use [fieldName tag value]: 0 = text, 1 = natural, 2 = Boolean.\n-- Base supplies arithmetic/Booleans; hostString? checks Unicode scalar text.\n\nnameMessage name = case hostString? name of\n ^true = case name of\n [] = \"Name is required.\"\n _ = \"\"\n _ = \"Name must be scalar text.\"\n\nemailMessage email = case hostString? email of\n ^true = case and? (contains? \"@\" email) (contains? \".\" email) of\n ^true = \"\"\n _ = \"Email must contain @ and a dot.\"\n _ = \"Email must be scalar text.\"\n\nageMessage age = case isNat? age of\n ^true = case and? (lte_? 18 age) (lte_? age 120) of\n ^true = \"\"\n _ = \"Age must be from 18 to 120.\"\n _ = \"Age must be a canonical natural.\"\n\ntermsMessage ^true = \"\"\ntermsMessage _ = \"Accept the terms.\"\n\nreport name email age terms = [\n [\"accepted\" 2 (and? (equal? name \"\") (and? (equal? email \"\") (and? (equal? age \"\") (equal? terms \"\"))))]\n [\"nameError\" 0 name] [\"emailError\" 0 email]\n [\"ageError\" 0 age] [\"termsError\" 0 terms]]\n\nmain name email age terms = report (nameMessage name) (emailMessage email) (ageMessage age) (termsMessage terms)\n", 656 "sourceSha256": "c91388231dfc9a04da02e750cfcbd873947f6cf761a59352446000421c7806dd", 657 "export": "main", 658 "arguments": [ 659 { 660 "type": "string", 661 "value": "James" 662 }, 663 { 664 "type": "string", 665 "value": "james@example.org" 666 }, 667 { 668 "type": "number", 669 "value": "21" 670 }, 671 { 672 "type": "bool", 673 "value": true 674 } 675 ], 676 "output": "tricu", 677 "mode": "pure", 678 "presets": { 679 "main": { 680 "arguments": [ 681 { 682 "type": "string", 683 "value": "James" 684 }, 685 { 686 "type": "string", 687 "value": "james@example.org" 688 }, 689 { 690 "type": "number", 691 "value": "21" 692 }, 693 { 694 "type": "bool", 695 "value": true 696 } 697 ], 698 "output": "tricu", 699 "mode": "pure" 700 } 701 }, 702 "environment": "empty" 703 }, 704 { 705 "id": "migration", 706 "title": "Profile migrations", 707 "category": "Practical Examples", 708 "hash": "730aeb8203ff504b7483999a6477a292b10871f82e94a77d3d5f7b49ecc3b6b3", 709 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 3f70d95cf1bae0928ee89acd2a9e0cdd8287796efae32a9f08d33e3dd0149c55 *\n!export v1\n!export v2\n\n-- Archived profile v0: [name email]. v1: adds schemaVersion and displayName.\n-- v2: also supplies marketingOptIn=false; existing data is preserved.\n-- Try result = v1 [\"James\" \"james@example.org\"].\n-- Change v1 to v2 to compare the two target schemas.\n-- A migration receipt can record the input identity, executable hash/export,\n-- output identity and wire schema. Running this demo stays entirely local.\n-- Records use [fieldName tag value]: 0 = text, 1 = natural, 2 = Boolean.\n-- Base supplies arithmetic/Booleans; hostString? checks Unicode scalar text.\n\nfailure message = [[\"status\" 0 \"error\"] [\"message\" 0 message]]\n\nmigrate version name email = case and? (hostString? name) (hostString? email) of\n ^true = case version of\n 1 = [[\"schemaVersion\" 1 1] [\"displayName\" 0 name] [\"email\" 0 email]]\n _ = [[\"schemaVersion\" 1 2] [\"displayName\" 0 name] [\"email\" 0 email]\n [\"marketingOptIn\" 2 false]]\n _ = failure \"Name and email must be scalar text.\"\n\nprofile _ (t _) = failure \"Expected a list.\"\nprofile _ [_ | (t _)] = failure \"Expected a list.\"\nprofile version [name email] = migrate version name email\nprofile _ _ = failure \"Expected [name email].\"\n\nv1 values = profile 1 values\nv2 values = profile 2 values\n", 710 "sourceSha256": "239e45bde8f461360a016bfbcca3111dc72da3b7dc237736d05b12dc6018e9f8", 711 "export": "v1", 712 "arguments": [ 713 { 714 "type": "list", 715 "value": [ 716 { 717 "type": "string", 718 "value": "James" 719 }, 720 { 721 "type": "string", 722 "value": "james@example.org" 723 } 724 ] 725 } 726 ], 727 "output": "tricu", 728 "mode": "pure", 729 "presets": { 730 "v1": { 731 "arguments": [ 732 { 733 "type": "list", 734 "value": [ 735 { 736 "type": "string", 737 "value": "James" 738 }, 739 { 740 "type": "string", 741 "value": "james@example.org" 742 } 743 ] 744 } 745 ], 746 "output": "tricu", 747 "mode": "pure" 748 }, 749 "v2": { 750 "arguments": [ 751 { 752 "type": "list", 753 "value": [ 754 { 755 "type": "string", 756 "value": "James" 757 }, 758 { 759 "type": "string", 760 "value": "james@example.org" 761 } 762 ] 763 } 764 ], 765 "output": "tricu", 766 "mode": "pure" 767 } 768 }, 769 "environment": "empty" 770 }, 771 { 772 "id": "parser", 773 "preparation": "native", 774 "environment": "empty", 775 "category": "Language & Runtime Explorations", 776 "title": "Parser combinators", 777 "hash": "355e41506f39e05da8c8a72a68aa810a486e062480322bac11af67e86980520e", 778 "source": "!import 6aa33a710955e5746f322775b9078f57ba4145f6b723ff4b76a292285e919823 *\n!import 14282088469cc420dd014700da06998f6bb8dcf990fc885a464c0a1e1858e20d *\n!import 7f87549d587fa8c3be88af0ea988db3adcc3b35f1ffb48973553491b075614db as N\n!export parseNames\n!export parsePrefix\n\n-- Lowercase names separated by commas. Try \"red,green\", \"red,,blue\",\n-- \"red,\" and \"red green\". A comma commits to another name.\n-- parseNames requires the entire input; parsePrefix exposes any remainder.\n-- Edit the parser or labels below; valid edits compile locally.\n-- Imports pin complete library interfaces, fetched only when compiling.\nnameParser = parserSatisfy (c : N.inRange c 97 122)\n |> parserSome\n |> parserLabel \"lowercase name\"\ncommaParser = parserThen (parserExpect 44) parserCommit\nnamesPrefix = parserSepBy1 nameParser commaParser\nnamesComplete = parserBefore namesPrefix <| parserLabel \"end of input\" parserEof\n\nreport reply = parserMatchReply\n (diagnostic rest consumed committed : [\n [\"status\" 0 \"error\"]\n [\"expected\" 0 (parserDiagnosticExpected diagnostic)]\n [\"remaining\" 0 rest]\n [\"consumed\" 2 consumed]\n [\"committed\" 2 committed]])\n (names rest consumed committed : [\n [\"status\" 0 \"ok\"]\n [\"names\" 0 (intercalate \", \" names)]\n [\"count\" 1 (length names)]\n [\"remaining\" 0 rest]\n [\"consumed\" 2 consumed]\n [\"committed\" 2 committed]])\n reply\nparseNames text = namesComplete text |> report\nparsePrefix text = namesPrefix text |> report\n", 779 "sourceSha256": "9c005993cf43c07ec5c7e4f7f420b2f63c66e605d9522ab4b7e044de5e6cb8df", 780 "export": "parseNames", 781 "arguments": [ 782 { 783 "type": "string", 784 "value": "red,green,blue" 785 } 786 ], 787 "output": "tricu", 788 "mode": "pure", 789 "presets": { 790 "parseNames": { 791 "arguments": [ 792 { 793 "type": "string", 794 "value": "red,green,blue" 795 } 796 ], 797 "output": "tricu", 798 "mode": "pure" 799 }, 800 "parsePrefix": { 801 "arguments": [ 802 { 803 "type": "string", 804 "value": "red,green,blue" 805 } 806 ], 807 "output": "tricu", 808 "mode": "pure" 809 } 810 } 811 }, 812 { 813 "id": "lexer", 814 "preparation": "native", 815 "environment": "empty", 816 "category": "Language & Runtime Explorations", 817 "title": "Located lexer scans", 818 "hash": "e756a481b3056a2e76f50316ccf77bfab8b15727b7bec899580c2eb919e668b2", 819 "source": "!import 6aa33a710955e5746f322775b9078f57ba4145f6b723ff4b76a292285e919823 *\n!import 14282088469cc420dd014700da06998f6bb8dcf990fc885a464c0a1e1858e20d *\n!import 816df1a32853750a1254112aff8ae8c55ca86bf70e809d226969ab1d43a6688b *\n!import 7f87549d587fa8c3be88af0ea988db3adcc3b35f1ffb48973553491b075614db as N\n!export scanWord\n!export scanWhitespace\n\n-- Positioned prefix scans, not whole-input parsers. The remainder begins\n-- at the first nonmatching character. Positions count source characters,\n-- not UTF-8 bytes or display columns: only LF advances the line.\n-- Edit a predicate below and evaluate scanWord \"hello,world\" to compare.\n-- scanWhitespace accepts space, LF and tab, but deliberately not CR.\nreport reply = let\n payload = parserReplyPayload reply\n end = snd payload\n in [\n [\"matched\" 0 (fst payload)]\n [\"remaining\" 0 (parserReplyRest reply)]\n [\"line\" 1 (lexerPositionLine end)]\n [\"column\" 1 (lexerPositionColumn end)]\n [\"offset\" 1 (lexerPositionOffset end)]\n [\"consumed\" 2 (parserReplyConsumed reply)]]\n\nscan predicate text = lexerScanWhile text lexerStart [] predicate (state character : pair character state)\n |> lexerMapScan reverse\n |> report\nscanWord text = scan (character : N.inRange character 97 122) text\nscanWhitespace text = scan (character : or? (equal? character 32)\n (or? (equal? character 9) (equal? character 10))) text\n", 820 "sourceSha256": "2a441d6d881acd6e34cd0caa8179ee1afefbce68c2e5ff1e44fb1768f69eeb3c", 821 "export": "scanWord", 822 "arguments": [ 823 { 824 "type": "string", 825 "value": "hello,world" 826 } 827 ], 828 "output": "tricu", 829 "mode": "pure", 830 "presets": { 831 "scanWord": { 832 "arguments": [ 833 { 834 "type": "string", 835 "value": "hello,world" 836 } 837 ], 838 "output": "tricu", 839 "mode": "pure" 840 }, 841 "scanWhitespace": { 842 "arguments": [ 843 { 844 "type": "string", 845 "value": " \n\tword" 846 } 847 ], 848 "output": "tricu", 849 "mode": "pure" 850 } 851 } 852 } 853 ]