This library supports making sum types without needing a separate enum like with object variants which gives multiple benefits of
- Being able to reuse field names
- Safely unpacking fields
- Pattern matching for quick comparisons
Getting Started
Types are created via the cased macro which supports both creating sum types from object declarations along with a shorthand for single field sumtypes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import casserole type IntOrString {.cased.} = object case of Int: val: int of String: # Notice that field names can be shared val: string # Short hand syntax if you just need a single anonymous field IntOrStringShort {.cased.} = tuple Int: int String: string |
15 16 17 | let isInt = IntOrString.Int(9) isString = IntOrString.String("Hello") |
Pattern Matching
To safely use these values, you use pattern matching via ?=, ?==, and `case`. Patterns are in the form Tag(field, ...) where Tag is the discriminator and field forms a pattern e.g.
- _: This matches anything, also doesn't put a variable into scope
- foo: This matches anything, but also puts a variable foo into scope with that value
- "someValue": This matches the exact value "someValue" (could be any expression), doesn't put anything into scope
18 19 20 21 22 23 24 25 26 27 28 | Int(_) ?= isInt # Be very careful with `?=`, it throws a defect if you're wrong # `?==` is safer since it only runs the if statement if it matched the pattern if Int(val) ?== isString: echo "It was an integer and it was: ", $val # this will never run tho... # Case statements are handy if you have lots of patterns case isInt of Int(1): echo "The value was 1" of String(x): echo "It was ", x of Int(_): echo "Was some other number" # You can have multiple patterns, placement gives precedence |
Supporting existing types
A feature of this library is being able to support existing types. For example, you can use std/options with pattern matching
Example:
import casserole import std/options let val = some("Hello") if Some(value) ?== val: echo valueThe following standard library modules are supported
Examples
For example, we could construct an Result type like so (Though we already have this in the library results: Result)
Example:
import casserole import casserole import std/random type # You must attach the `{.cased.}` pragma Result*[T, E] {.cased.} = object case of Ok: # Throw your fields into each branch value: T of Error: error: E # Looks like any other type proc possiblyFails(): Result[int, string] = if sample([true, false]): # Constructed by referencing the type and the branch Result[int, string].Ok(rand(100)) else: Result[int, string].Error("Failed for some reason =(") let res = possiblyFails() # Can extract via pattern matching with `if`/`case` statements if Ok(randVal) ?== res: echo "We got: " & $randVal # Case statements check for exhaustiveness case res of Ok(randVal): echo "Was something" of Error(error): echo "It failed"
Types
CasedObject[D; R] = concept proc currentBranch(val: Self): D ## This must return the current discriminator value for an object proc getBranch(val: Self; branch: static[D]): R ## This must return the value of a branch. Can raise a fieldDefect if in wrong state
- This gets implemented for custom types to integrate them into cased pattern matching. Source Edit
CaseObject[D] = object of RootObj
- Type class for accepting a case object. D is the discriminator for the fields Source Edit
Procs
proc `==`[T: CaseObject](left, right: T): bool
- Compares two CaseObject and considers them equal if they have the same branch and values Source Edit
Macros
macro `?==`(lhs: untyped; rhs: untyped): bool
-
Like ?= except doesn't raise an error. This is meant to be used inside if statements for unpacking a value. Variables will still be added to scope for wrong branch, but won't be initialised
Example:
import std/options let opt = none(string) # Won't error if wrong branch if Some(value) ?== opt: # `value` can only be accessed inside if statement echo "Great, we have " & $value
Source Edit macro `case`(n: CasedObject | CaseObject): untyped
-
Macro that adds support for pattern matching via case statement/expression. This supports the same syntax as ?=
Example:
import std/options case some(9) of Some(_): echo "Have something" of None(): echo "Have nothing"
Source Edit macro cased(inp: untyped): untyped
-
Generates a case class object based on RFC#559
Example:
type Optional[T] {.cased.} = object case of Some: value: T of None: discard
There is also a shorthand syntax using tuples if you don't need multiple fields e.g. This is equivilant notationExample:
type Optional[T] {.cased.} = tuple[ Some: T, None: nil ]
Source Edit macro unrollEnum(body: ForLoopStmt): untyped
- Macro that unrolls an enum and runs the loop body for each instance. This is unrolled at compile time, so each iteration gets a static version of the value. Be careful with large enums since the body is copied for each enum value Source Edit
Templates
template currentBranch[D; T: CaseObject[D]](c: T): D
- Returns the current state that a CaseObject is in Source Edit
template getBranch[T: CaseObject](c: T; branch: untyped): tuple
- Generic function that gets the branch value for any CaseObject Source Edit