catalogue.json (38922B)
1 [ 2 { 3 "id": "names-values", 4 "title": "Names and values", 5 "category": "Basic Examples", 6 "hash": "8d1b7e1f6dfefe983167ee8d9631476caaa0c3dc689a85628e9ca627ebaef6d0", 7 "source": "!export greeting\n!export answer\n\n-- Give a value a name.\ngreeting = \"Hello, world!\"\n-- Evaluate greeting with Text.\n-- Result: Hello, world!\n-- Try changing the words, then Evaluate again.\n\n-- Numbers can have names too.\nanswer = 42\n-- Choose answer in Definition.\n-- It selects Natural number; the result is 42.\n\n-- Exercise: change answer to your favourite number and evaluate it.\n-- The !export lines make our names available to the playground.\n-- Edits compile after a pause; Evaluate runs the expression.\n-- Next: Doing arithmetic.\n", 8 "sourceSha256": "77d8c61538436b86f620612a8502ba2980838b6e370cf71f6a364ff851517f3e", 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 }, 25 "environment": "empty" 26 }, 27 { 28 "id": "arithmetic", 29 "title": "Doing arithmetic", 30 "category": "Basic Examples", 31 "hash": "38110ab45a75a9c8771d6422277cb3083e667a4368aa33df0b43af672f17375e", 32 "source": "!import 7f87549d587fa8c3be88af0ea988db3adcc3b35f1ffb48973553491b075614db as Math\n!export *\n\n-- Add the prices of two items.\ntotal = Math.add 5 7\n-- Evaluate total with Natural number.\n-- Result: 12.\n-- Add import supplies the library identity; Math is our short name.\n\n-- Function calls use spaces: Math.multiply takes two numbers.\nproduct = Math.multiply 5 7\n\n-- Parentheses make one calculation the input to another.\ngrouped = Math.multiply (Math.add 5 7) 2\n\n-- Try product (35), then grouped (24), with Natural number.\n-- Exercise: change total to add the prices of two things you would buy.\n-- Numbers here are whole numbers starting at zero.\n-- Next: Your first function.\n", 33 "sourceSha256": "67bfda56d700499e9f484f73d670c22156842fc963798127284be1f8482e2ba8", 34 "export": "total", 35 "arguments": [], 36 "output": "number", 37 "mode": "pure", 38 "presets": { 39 "total": { 40 "arguments": [], 41 "output": "number", 42 "mode": "pure" 43 }, 44 "product": { 45 "arguments": [], 46 "output": "number", 47 "mode": "pure" 48 }, 49 "grouped": { 50 "arguments": [], 51 "output": "number", 52 "mode": "pure" 53 } 54 }, 55 "environment": "empty" 56 }, 57 { 58 "id": "functions", 59 "title": "Your first function", 60 "category": "Basic Examples", 61 "hash": "be5f4675c9b0b6d9c093fe44a9695da9f428c88d62b0f4f1340017067fa81fa9", 62 "source": "!import 7f87549d587fa8c3be88af0ea988db3adcc3b35f1ffb48973553491b075614db as Math\n!export *\n\n-- Reuse a calculation with different inputs.\ndouble number = Math.multiply number 2\n-- Evaluate double 6 with Natural number.\n-- Result: 12.\n-- Add import supplies Math, including multiplication.\n\n-- number is a parameter: each call supplies its value.\n-- Try double 9: the same definition now returns 18.\n\n-- A function can also take more than one input.\nadd left right = Math.add left right\n-- Try add 4 7 with Natural number: the result is 11.\n\n-- Exercise: change double to multiply by 3, and give it the name triple.\n-- Then evaluate triple 6. Update the expression to use your new name.\n-- Next: Working with lists.\n", 63 "sourceSha256": "44560b2f0d1df0ef24b853cc0e9a31a9a57108d8cfa56653a1840d264e498a24", 64 "export": "double", 65 "arguments": [ 66 { 67 "type": "number", 68 "value": "6" 69 } 70 ], 71 "output": "number", 72 "mode": "pure", 73 "presets": { 74 "double": { 75 "arguments": [ 76 { 77 "type": "number", 78 "value": "6" 79 } 80 ], 81 "output": "number", 82 "mode": "pure" 83 }, 84 "add": { 85 "arguments": [ 86 { 87 "type": "number", 88 "value": "4" 89 }, 90 { 91 "type": "number", 92 "value": "7" 93 } 94 ], 95 "output": "number", 96 "mode": "pure" 97 } 98 }, 99 "environment": "empty" 100 }, 101 { 102 "id": "lists", 103 "title": "Working with lists", 104 "category": "Basic Examples", 105 "hash": "3cc5d36c5b70a20362117e3349aa044efca8adca56105424965065395a9f3e76", 106 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 as List\n!export count\n!export total\n\n-- Collect values in a list.\nnumbers = [2 4 6]\ncount = List.length numbers\ntotal = List.sum numbers\n\n-- Evaluate count with Natural number: the result is 3.\n-- Spaces separate items. [] is the empty list.\n-- Add import supplies List, including length and sum.\n\n-- Try total with Natural number: the result is 12.\n-- Exercise: add 8 to numbers. Check count (4) and total (20).\n-- Then try numbers = []: both results become 0.\n-- Next: Transforming a list.\n", 107 "sourceSha256": "8d1dcc3840ccbfff3e2b64de18ba96ea81346b317dded4b7d3414657acf4fa7f", 108 "export": "count", 109 "arguments": [], 110 "output": "number", 111 "mode": "pure", 112 "presets": { 113 "count": { 114 "arguments": [], 115 "output": "number", 116 "mode": "pure" 117 }, 118 "total": { 119 "arguments": [], 120 "output": "number", 121 "mode": "pure" 122 } 123 }, 124 "environment": "empty" 125 }, 126 { 127 "id": "transform-list", 128 "title": "Transforming a list", 129 "category": "Basic Examples", 130 "hash": "9f10d772b42628acfe843eec7524d8de1ba9ea4344d3952c6c091c98c4d1c72d", 131 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 as List\n!import 7f87549d587fa8c3be88af0ea988db3adcc3b35f1ffb48973553491b075614db as Math\n!export total\n!export double\n\n-- Apply the same function to every item.\ndouble number = Math.multiply number 2\nnumbers = [2 4 6]\n\n-- map takes a function, then a list. Here it produces [4 8 12].\ndoubled = List.map double numbers\ntotal = List.sum doubled\n-- Evaluate total with Natural number: the result is 24.\n-- Add import supplies List and Math.\n\n-- Try double 5 with Natural number: the result is 10.\n-- Exercise: make double multiply by 3. The new total should be 36.\n-- Next: Making a choice.\n", 132 "sourceSha256": "8e33d918846404760b398b473a93effad390b1486d5c2b053078170226cb8da9", 133 "export": "total", 134 "arguments": [], 135 "output": "number", 136 "mode": "pure", 137 "presets": { 138 "total": { 139 "arguments": [], 140 "output": "number", 141 "mode": "pure" 142 }, 143 "double": { 144 "arguments": [ 145 { 146 "type": "number", 147 "value": "5" 148 } 149 ], 150 "output": "number", 151 "mode": "pure" 152 } 153 }, 154 "environment": "empty" 155 }, 156 { 157 "id": "choices", 158 "title": "Making a choice", 159 "category": "Basic Examples", 160 "hash": "1972c98bfe599fef2a72cc946401a5cc9280026a8c46fdd7c8ccdc7093ee90cc", 161 "source": "!export message\n\n-- Choose an answer by matching an input.\nmessage 0 = \"Your basket is empty.\"\nmessage 1 = \"You have one item.\"\nmessage _ = \"You have several items.\"\n\n-- Evaluate message 0 with Text: Your basket is empty.\n-- These equations are tried from top to bottom.\n-- _ matches any remaining input: this is the fallback.\n-- Try message 1, then message 4, using Text.\n\n-- Exercise: add a message for 2 items before the fallback equation.\n-- Keep the equations together so they define one function.\n-- Next: try a Practical Example, or browse Language reference.\n", 162 "sourceSha256": "85ec05530278f77521c28f25437ff85b9418656eb7174a5ca7aba3abb826167f", 163 "export": "message", 164 "arguments": [ 165 { 166 "type": "number", 167 "value": "0" 168 } 169 ], 170 "output": "string", 171 "mode": "pure", 172 "presets": { 173 "message": { 174 "arguments": [ 175 { 176 "type": "number", 177 "value": "0" 178 } 179 ], 180 "output": "string", 181 "mode": "pure" 182 } 183 }, 184 "environment": "empty" 185 }, 186 { 187 "id": "language-reference", 188 "title": "Language reference", 189 "category": "Language & Runtime Explorations", 190 "hash": "86717d7f9d69fe730c6c6172c42c49895623aa070bf675e1b5f751adb30616c1", 191 "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 / Natural number: 42. Choose a definition for its preset.\n-- Full contracts: follow For AI assistants in the footer to Language features.\n-- Each comment below gives an expression and its interpretation.\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 qualified 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 Add import 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", 192 "sourceSha256": "5d4035e52ef2f9f85278d6431fc5be43df4e7badce7393930237fc1deb1c4fb8", 193 "export": "answer", 194 "arguments": [], 195 "output": "number", 196 "mode": "pure", 197 "presets": { 198 "answer": { 199 "arguments": [], 200 "output": "number", 201 "mode": "pure" 202 }, 203 "identity": { 204 "arguments": [ 205 { 206 "type": "number", 207 "value": "7" 208 } 209 ], 210 "output": "number", 211 "mode": "pure" 212 }, 213 "first": { 214 "arguments": [ 215 { 216 "type": "number", 217 "value": "7" 218 }, 219 { 220 "type": "number", 221 "value": "9" 222 } 223 ], 224 "output": "number", 225 "mode": "pure" 226 }, 227 "leaf": { 228 "arguments": [], 229 "output": "tricu", 230 "mode": "pure" 231 }, 232 "shapes": { 233 "arguments": [], 234 "output": "tricu", 235 "mode": "pure" 236 }, 237 "literals": { 238 "arguments": [], 239 "output": "tricu", 240 "mode": "pure" 241 }, 242 "addTwo": { 243 "arguments": [ 244 { 245 "type": "number", 246 "value": "5" 247 } 248 ], 249 "output": "number", 250 "mode": "pure" 251 }, 252 "describe": { 253 "arguments": [ 254 { 255 "type": "number", 256 "value": "7" 257 } 258 ], 259 "output": "string", 260 "mode": "pure" 261 }, 262 "keep": { 263 "arguments": [ 264 { 265 "type": "tree", 266 "value": "200" 267 } 268 ], 269 "output": "tricu", 270 "mode": "pure" 271 }, 272 "local": { 273 "arguments": [ 274 { 275 "type": "number", 276 "value": "6" 277 } 278 ], 279 "output": "number", 280 "mode": "pure" 281 }, 282 "viaWhere": { 283 "arguments": [ 284 { 285 "type": "number", 286 "value": "6" 287 } 288 ], 289 "output": "number", 290 "mode": "pure" 291 }, 292 "copy": { 293 "arguments": [ 294 { 295 "type": "list", 296 "value": [ 297 { 298 "type": "number", 299 "value": "1" 300 }, 301 { 302 "type": "number", 303 "value": "2" 304 }, 305 { 306 "type": "number", 307 "value": "3" 308 } 309 ] 310 } 311 ], 312 "output": "bytes", 313 "mode": "pure" 314 }, 315 "firstOr": { 316 "arguments": [ 317 { 318 "type": "number", 319 "value": "99" 320 }, 321 { 322 "type": "list", 323 "value": [ 324 { 325 "type": "number", 326 "value": "5" 327 } 328 ] 329 } 330 ], 331 "output": "number", 332 "mode": "pure" 333 }, 334 "localCopy": { 335 "arguments": [ 336 { 337 "type": "list", 338 "value": [ 339 { 340 "type": "number", 341 "value": "4" 342 }, 343 { 344 "type": "number", 345 "value": "5" 346 } 347 ] 348 } 349 ], 350 "output": "bytes", 351 "mode": "pure" 352 }, 353 "checked": { 354 "arguments": [ 355 { 356 "type": "number", 357 "value": "42" 358 } 359 ], 360 "output": "number", 361 "mode": "pure" 362 }, 363 "sequenced": { 364 "arguments": [ 365 { 366 "type": "number", 367 "value": "9" 368 } 369 ], 370 "output": "number", 371 "mode": "pure" 372 } 373 }, 374 "environment": "empty" 375 }, 376 { 377 "id": "greet", 378 "title": "A conversation", 379 "category": "Practical Examples", 380 "hash": "0811457d501113ee11194f90e279ebb4ef9d548c70cb48d146be03eb9af7505f", 381 "source": "!import 2d23be85d277457fd36c84f5eaa227e94e1c4efec281ff73017aaee109096f49 as IO\n!export main\n\n-- A conversation built from the IO library's actions and explicit bind operator.\n-- Evaluate main in Browser IO mode, then answer in the terminal input.\n-- The returned name is also displayed as Text. Try changing the greeting.\n-- Importing IO does not grant filesystem or network access in the browser.\nmain = (IO.io (do IO.bind\n IO.putStr \"What is your name?\\n\"\n name <- IO.getLine\n IO.putStr \"Hello, \"\n IO.putStr name\n IO.putStr \"!\\n\"\n IO.pure name))\n", 382 "sourceSha256": "3b9835aec1f3b987ad954cd235a7c0517170d3f7e236b15d60ce9a3a809c74c4", 383 "export": "main", 384 "arguments": [], 385 "output": "string", 386 "mode": "io", 387 "presets": {}, 388 "environment": "empty" 389 }, 390 { 391 "id": "microscope", 392 "title": "Tree microscope", 393 "category": "Language & Runtime Explorations", 394 "hash": "493a125b649639c0df8348b4aeb3a455b7139cae0634ba114866c48813500bc5", 395 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 as B\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 = B.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.\ncount tree acc = (B.triage\n (B.succ acc)\n (child : count child (B.succ acc))\n (left right : count right (count left (B.succ acc)))\n tree)\n\nsize tree = count tree 0\n\nsame a b = (B.triage\n (B.triage B.true (ignored : B.false) (ignored ignored2 : B.false) b)\n (x : B.triage B.false (y : same x y) (ignored ignored2 : B.false) b)\n (x y : B.triage B.false (ignored : B.false) (u v : B.and? (same x u) (same y v)) b)\n a)\n", 396 "sourceSha256": "0dd0bb5444662200cf3556dd7a81e65570b5d3b34d4b3e6b17e6d9c36d3d1114", 397 "export": "size", 398 "arguments": [ 399 { 400 "type": "tree", 401 "value": "21100" 402 } 403 ], 404 "output": "number", 405 "mode": "pure", 406 "presets": { 407 "shape": { 408 "arguments": [ 409 { 410 "type": "tree", 411 "value": "21100" 412 } 413 ], 414 "output": "string", 415 "mode": "pure" 416 }, 417 "size": { 418 "arguments": [ 419 { 420 "type": "tree", 421 "value": "21100" 422 } 423 ], 424 "output": "number", 425 "mode": "pure" 426 }, 427 "same": { 428 "arguments": [ 429 { 430 "type": "tree", 431 "value": "21100" 432 }, 433 { 434 "type": "tree", 435 "value": "21100" 436 } 437 ], 438 "output": "bool", 439 "mode": "pure" 440 } 441 }, 442 "environment": "empty" 443 }, 444 { 445 "id": "rgb", 446 "title": "RGB validator", 447 "category": "Practical Examples", 448 "hash": "b8c0e21c09f63511794cc62827a6b231e62a55f7e779022a1b740aaba29b0368", 449 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 7a24af56a9ce9b947cadcafd3a9da127b1507275bfac62b9fa55e27490b176dc as Binary\n!export main\n\n-- Validate an RGB list using the library's canonical-byte predicate.\n-- Try main [64 128 255], main [0 256 255], or main [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? (Binary.binaryByte? r) (and? (Binary.binaryByte? g) (Binary.binaryByte? b)) of\n ^true = \"RGB triple accepted.\"\n _ = \"A channel is not a canonical byte (0-255).\"\nmain _ = \"Expected exactly three channels.\"\n", 450 "sourceSha256": "33da7deb793df280f59b47913b57562dbb2820525f19451ba5e4042219ef559e", 451 "export": "main", 452 "arguments": [ 453 { 454 "type": "bytes", 455 "value": [ 456 64, 457 128, 458 255 459 ] 460 } 461 ], 462 "output": "string", 463 "mode": "pure", 464 "presets": {}, 465 "environment": "empty" 466 }, 467 { 468 "id": "shipping", 469 "title": "Shipping policies", 470 "category": "Practical Examples", 471 "hash": "4aa9ce9f19dfce87ecb3f6bd0a2e881dafcbcd3d2e4b4075befbb378d80833ea", 472 "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-- Select v1/v2 with the same cart: 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", 473 "sourceSha256": "d6d9378ab39733a8aabaf4f6a25851350e99fa81607bf05a64266301e2712f5a", 474 "export": "v1", 475 "arguments": [ 476 { 477 "type": "string", 478 "value": "domestic" 479 }, 480 { 481 "type": "number", 482 "value": "8000" 483 }, 484 { 485 "type": "number", 486 "value": "1500" 487 } 488 ], 489 "output": "record", 490 "mode": "pure", 491 "presets": { 492 "v1": { 493 "arguments": [ 494 { 495 "type": "string", 496 "value": "domestic" 497 }, 498 { 499 "type": "number", 500 "value": "8000" 501 }, 502 { 503 "type": "number", 504 "value": "1500" 505 } 506 ], 507 "output": "record", 508 "mode": "pure" 509 }, 510 "v2": { 511 "arguments": [ 512 { 513 "type": "string", 514 "value": "domestic" 515 }, 516 { 517 "type": "number", 518 "value": "8000" 519 }, 520 { 521 "type": "number", 522 "value": "1500" 523 } 524 ], 525 "output": "record", 526 "mode": "pure" 527 } 528 }, 529 "environment": "empty" 530 }, 531 { 532 "id": "events", 533 "title": "Event adapters", 534 "category": "Practical Examples", 535 "hash": "8b51693c63658d59c8083ec1c95cf1f6565ce5799791f540e92caf508613c9fb", 536 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 3f70d95cf1bae0928ee89acd2a9e0cdd8287796efae32a9f08d33e3dd0149c55 as A\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 the two exports: their presets describe the same order.created 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; A.hostString? checks Unicode scalar text.\n\nfailure message = [[\"status\" 0 \"error\"] [\"message\" 0 message]]\n\nnormalized event customer cents = case A.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", 537 "sourceSha256": "ca11d9a7e20bfe228545c43f50aed57089c020eead516e23b67d909fae8923de", 538 "export": "vendorA", 539 "arguments": [ 540 { 541 "type": "list", 542 "value": [ 543 { 544 "type": "string", 545 "value": "purchase" 546 }, 547 { 548 "type": "string", 549 "value": "James" 550 }, 551 { 552 "type": "number", 553 "value": "2499" 554 } 555 ] 556 } 557 ], 558 "output": "record", 559 "mode": "pure", 560 "presets": { 561 "vendorA": { 562 "arguments": [ 563 { 564 "type": "list", 565 "value": [ 566 { 567 "type": "string", 568 "value": "purchase" 569 }, 570 { 571 "type": "string", 572 "value": "James" 573 }, 574 { 575 "type": "number", 576 "value": "2499" 577 } 578 ] 579 } 580 ], 581 "output": "record", 582 "mode": "pure" 583 }, 584 "vendorB": { 585 "arguments": [ 586 { 587 "type": "list", 588 "value": [ 589 { 590 "type": "string", 591 "value": "James" 592 }, 593 { 594 "type": "bool", 595 "value": false 596 }, 597 { 598 "type": "number", 599 "value": "2499" 600 } 601 ] 602 } 603 ], 604 "output": "record", 605 "mode": "pure" 606 } 607 }, 608 "environment": "empty" 609 }, 610 { 611 "id": "registration", 612 "title": "Registration validator", 613 "category": "Practical Examples", 614 "hash": "679733e17a86cc5e3f5282ca824cad4c9c0febd260e51f6a87e16dd14c02c45a", 615 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 3f70d95cf1bae0928ee89acd2a9e0cdd8287796efae32a9f08d33e3dd0149c55 as A\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-- 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; A.hostString? checks Unicode scalar text.\n\nnameMessage name = case A.hostString? name of\n ^true = (case name of\n [] = \"Name is required.\"\n _ = \"\")\n _ = \"Name must be scalar text.\"\n\nemailMessage email = case A.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", 616 "sourceSha256": "531866c6b2e7e42ecbd4ae285c502b59321ad31fd2b82b36d8a139938592ac31", 617 "export": "main", 618 "arguments": [ 619 { 620 "type": "string", 621 "value": "James" 622 }, 623 { 624 "type": "string", 625 "value": "james@example.org" 626 }, 627 { 628 "type": "number", 629 "value": "21" 630 }, 631 { 632 "type": "bool", 633 "value": true 634 } 635 ], 636 "output": "record", 637 "mode": "pure", 638 "presets": { 639 "main": { 640 "arguments": [ 641 { 642 "type": "string", 643 "value": "James" 644 }, 645 { 646 "type": "string", 647 "value": "james@example.org" 648 }, 649 { 650 "type": "number", 651 "value": "21" 652 }, 653 { 654 "type": "bool", 655 "value": true 656 } 657 ], 658 "output": "record", 659 "mode": "pure" 660 } 661 }, 662 "environment": "empty" 663 }, 664 { 665 "id": "migration", 666 "title": "Profile migrations", 667 "category": "Practical Examples", 668 "hash": "730aeb8203ff504b7483999a6477a292b10871f82e94a77d3d5f7b49ecc3b6b3", 669 "source": "!import bd2c14d6db6b69308a5252a8e839705a55d30e815f012900b094315263e08d81 *\n!import 3f70d95cf1bae0928ee89acd2a9e0cdd8287796efae32a9f08d33e3dd0149c55 as A\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-- Select v1/v2 with the same input 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; A.hostString? checks Unicode scalar text.\n\nfailure message = [[\"status\" 0 \"error\"] [\"message\" 0 message]]\n\nmigrate version name email = case and? (A.hostString? name) (A.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", 670 "sourceSha256": "66ee7bc1a9005b5f1d12f168b7866231ad7f523cf34f52b63958cb155a5c49b5", 671 "export": "v1", 672 "arguments": [ 673 { 674 "type": "list", 675 "value": [ 676 { 677 "type": "string", 678 "value": "James" 679 }, 680 { 681 "type": "string", 682 "value": "james@example.org" 683 } 684 ] 685 } 686 ], 687 "output": "record", 688 "mode": "pure", 689 "presets": { 690 "v1": { 691 "arguments": [ 692 { 693 "type": "list", 694 "value": [ 695 { 696 "type": "string", 697 "value": "James" 698 }, 699 { 700 "type": "string", 701 "value": "james@example.org" 702 } 703 ] 704 } 705 ], 706 "output": "record", 707 "mode": "pure" 708 }, 709 "v2": { 710 "arguments": [ 711 { 712 "type": "list", 713 "value": [ 714 { 715 "type": "string", 716 "value": "James" 717 }, 718 { 719 "type": "string", 720 "value": "james@example.org" 721 } 722 ] 723 } 724 ], 725 "output": "record", 726 "mode": "pure" 727 } 728 }, 729 "environment": "empty" 730 }, 731 { 732 "id": "parser", 733 "preparation": "native", 734 "environment": "empty", 735 "title": "Parser combinators", 736 "hash": "355e41506f39e05da8c8a72a68aa810a486e062480322bac11af67e86980520e", 737 "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 = parserLabel \"lowercase name\" (parserSome (parserSatisfy (c : N.inRange c 97 122)))\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 = report (namesComplete text)\nparsePrefix text = report (namesPrefix text)\n", 738 "sourceSha256": "18aa0a464dabb595749abdab5edb1bb11c4356c0e753cf0985a644901bb5c241", 739 "export": "parseNames", 740 "arguments": [ 741 { 742 "type": "string", 743 "value": "red,green,blue" 744 } 745 ], 746 "output": "record", 747 "mode": "pure", 748 "presets": { 749 "parseNames": { 750 "arguments": [ 751 { 752 "type": "string", 753 "value": "red,green,blue" 754 } 755 ], 756 "output": "record", 757 "mode": "pure" 758 }, 759 "parsePrefix": { 760 "arguments": [ 761 { 762 "type": "string", 763 "value": "red,green,blue" 764 } 765 ], 766 "output": "record", 767 "mode": "pure" 768 } 769 }, 770 "category": "Language & Runtime Explorations" 771 }, 772 { 773 "id": "lexer", 774 "preparation": "native", 775 "environment": "empty", 776 "title": "Located lexer scans", 777 "hash": "e756a481b3056a2e76f50316ccf77bfab8b15727b7bec899580c2eb919e668b2", 778 "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 = report (lexerMapScan reverse\n (lexerScanWhile text lexerStart [] predicate (state character : pair character state)))\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", 779 "sourceSha256": "ec68af4102c16ef56be01e78a3288bea57af71ca57ab35374a0d12005b5a10a1", 780 "export": "scanWord", 781 "arguments": [ 782 { 783 "type": "string", 784 "value": "hello,world" 785 } 786 ], 787 "output": "record", 788 "mode": "pure", 789 "presets": { 790 "scanWord": { 791 "arguments": [ 792 { 793 "type": "string", 794 "value": "hello,world" 795 } 796 ], 797 "output": "record", 798 "mode": "pure" 799 }, 800 "scanWhitespace": { 801 "arguments": [ 802 { 803 "type": "string", 804 "value": " \n\tword" 805 } 806 ], 807 "output": "record", 808 "mode": "pure" 809 } 810 }, 811 "category": "Language & Runtime Explorations" 812 } 813 ]