Matching values with case/~, destructuring with .bind:, and raising and catching errors.
Nav: Foundations · Blocks & control · Objects · Patterns & errors · Concurrency & iteration · Networking & the web · Types · Tooling · Library & reference · Appendices
caseRules
subject.case:{ .when:cond do:result; … .default:fallback }— tests eachcondagainstsubjectwith the~operator; the first match wins. With no match and nodefault:, the result isnil. Each arm is an ordinary statement, so end it with;— a following line that starts with.when:'s leading dot would otherwise continue the previous arm (§2).do:(anddefault:) accept either a block (the block receives the subject as its argument) or a plain value (used as the result).- The
~match protocol:a ~ bisa.~:(b)— the matcher is the left operand, so dispatch is class-first ona's~:(define~:on your own class to customize). Built-in matchers: a Class tests is-instance-of (Integer ~ 5), a Regex tests a match against the string (#/…/ ~ str), a Block runs as a predicate overb, a range tests membership, and the defaultObject#~:is==:equality. (Because the matcher is on the left,caseputs thecondfirst:cond ~ subject.)
var score = 87 var grade = score.case:{ .when:(90..101) do:'A'; "* range membership .when:(80..90) do:'B'; .when:{ |n| n < 0 } do:'invalid'; "* predicate block, gets the subject .default:'F' } grade; "* -> 'B' var name = 'Ada' name.case:{ .when:#/^[A-Z]+$/ do:{ 'shouting'.print }; "* regex match .when:'Ada' do:{ 'hi Ada'.print }; "* equality .default:{ 'unknown'.print } }
The same ~ operator works standalone, with the matcher on the left: (1..10) ~ 5, #/b/ ~ 'abc', TypeError ~ value, { |n| n > 0 } ~ x.
bind: familyRules
list.bind:{ |a b| … }calls the block with the list's elements positionally — a two-parameter block gets the first two elements, a missing element bindsnil, extras are ignored. Answers the block's value.map.bind:{ |w h| … }binds each parameter by name. The lookup runs backward, parameter → key — the parameter's name as a String key first, then as a Symbol key; an absent key bindsnil. (A Map key need not be an identifier, but every identifier is a candidate key — so the parameters drive the lookup, never the other way around.)regex.match:'…'answers a Match, ornilwhen the pattern misses — the nil-guard is the miss test.match.bind:{ … }binds a parameter named after a named capture group ((?<name>…)) to that group and any other parameter positionally (first parameter → group 1); a group that did not participate bindsnil.- A Match also reads directly:
sis the whole matched text,at:one group by index (1-based; 0 is the whole match) or by name (String or Symbol), andcaptureslists the groups in order.
#(3 4).bind:{ |w h| w * h } "* -> 12 #{'w': 5 'h': 4}.bind:{ |w h| w * h } "* -> 20 ('1/2/3'.split:'/').bind:{ |a b c| a + c } "* -> '13' var m = #/(?<user>\w+)@(?<host>[\w.]+)/.match:'ada@example.org' m.bind:{ |host user| %'%{host} gets mail for %{user}' } "* -> 'example.org gets mail for ada' m.at:'user'; "* -> 'ada' m.at:2; "* -> 'example.org' (#/(\d+)-(\d+)/.match:'10-20').bind:{ |lo hi| hi.to_integer - lo.to_integer } "* -> 10 #/x/.match:'abc' "* -> nil
Rules
value.throwthrows any value. TheErrorclasses add class-side convenience constructors:Error.throw:'msg'andError.throw:'msg' payload:pbuild an instance and throw it.{ … }.catch:{ |e| … }runs the receiver block; if it throws, the thrown value is passed to the catch block, whose result becomes the value.{ … }.catch:{ |e| … } finally:{ … }additionally runsfinally:always (on success or failure).- Typed catch. A typed handler param —
catch:{ |e:IoError| … }— only catches when the thrown value is (a subtype of) that type; a non-match re-raises to an enclosingcatch:. An untyped|e|(≡|e:Object|) is a catch-all.- Multiple handlers by type. Chain
catch:keywords:{ … }.catch:{ |e:IoError| … } catch:{ |e:Error| … } finally:{ … }. Handlers are tried in source order, first match wins — so write them most-specific → least-specific, with any untyped catch-all last (a broad handler placed first shadows the narrower ones below it). This first-match ordering is a deliberate exception to Quoin's otherwise order-independent multimethod dispatch: a handler's type lives on a runtime block, not a scored method chain, so there is no specificity order to fall back on. (Inside a single handler you can still branch withcase/~:e.case:{ .when:TypeError do:… }.)
var amount = -5 var result = { (amount < 0).if:{ ArgumentError.throw:'amount must be >= 0' }; .process:amount "* reached only when the check passes }.catch:{ |e:ArgumentError| ('bad input: ' + e.message).print; 0 } catch:{ |e:IoError| ('io failed: ' + e.message).print; -1 } finally:{ 'done'.print } "* anything that isn't an ArgumentError or IoError re-raises automatically — "* most-specific handler first, no explicit re-throw needed. result "* -> 0
Internal failures surface as the matching Quoin error type — e.g. an out-of-range index or a type mismatch becomes a catchable TypeError/IndexError, and sending an unknown selector becomes a MessageNotUnderstood — each with a message you can read.
Three statement-only markers hold a place for code that isn't there yet — the todo!() family of Quoin. They are statements, not expressions (var x = ... is a parse error):
...— "not written yet": throws a typedNotImplementedError.!!!— "can NEVER execute": throws a typedUnreachableError. Reaching one is a logic error worth crashing over.???— "shouldn't get here, but keep going": prints afile:line:col: warning:line to the Log — with the placeholder's real source location — and execution continues (its statement value isnil).
{ ... }.catch:{ |e:NotImplementedError| e.message } "* -> not implemented { !!! }.catch:{ |e:UnreachableError| e.message } "* -> reached unreachable code
Both throwing forms are ordinary Error subclasses: a plain catch:{ |e:Error| … } catches them, traces point at the placeholder, and a test can pin one with .does:{ ... } throw:NotImplementedError.
Stack traces: uncaught errors print a highlighted trace (with source snippets). The mechanics are an implementation detail; nothing in the language surface depends on them.