From 5e66ce06cea58453c6b75ab9c23ddb563bb76272 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Fri, 20 Jun 2025 15:33:54 +0300 Subject: [PATCH] blog(intro-cab): unlisted draft --- site/blog/2025-06-20-intro-cab.md | 220 ++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 site/blog/2025-06-20-intro-cab.md diff --git a/site/blog/2025-06-20-intro-cab.md b/site/blog/2025-06-20-intro-cab.md new file mode 100644 index 0000000..7cf81f5 --- /dev/null +++ b/site/blog/2025-06-20-intro-cab.md @@ -0,0 +1,220 @@ +--- +title: An introduction to the Cab expression language +unlisted: true +--- + +Here is a small Cab snippet, taken from the standard library: + +```swift +@Any = (@_), {[(1)]} + +@symbol = {[(2)]} @name & String => {[(3)]} { + {[(4)]} @`\(magic.name)` = name, +}, + +@nominate = @name & String => { + @`\(magic.name)` = name, + @`\(magic.value)` = value, +}, + + {[(5)]} +@None = symbol "None", + +@Some = nominate "Some", + +@Option = { + {[(6)]} + @`\(magic.call)` = @value & Any => None | Some value, + + @unwrapOr = @default & Any => @option & Option (TypeOf default) => + if Some @value = option then + value + else + default, + + @or = @alternative & Option Any => @option & TypeOf alternative => + if Some Any = option then + option + else + alternative, +}, +``` + +It may look confusing at first, but the way Cab works is refreshingly simple & +at the end of this blog post, you'll understand how "typing", "pattern +matching", and other things that don't actually exist in Cab work. + +Let's start from the top: + +

+ +```swift +@Any = (@_), {[(1)]} +``` + +

+ +Cab doesn't have "declarations", or "pattern matching". This is a _literal_ +comparision operation. Exactly the same as the `==` operator in most languages. + +But then, how do we even declare anything in the local scope? How do we address +values by name, instead of inlining them all? + +The way Cab answers this question is interesting (and original, I have not seen +anything like it before): Bindings as values! + +In Cab, you can create a binding value with the `@` syntax. So +here, `@Any` is a binding value. + +And the way you use bindings (aka, binds) in Cab is simple: You compare them +using the comparision operator, `=` or `!=`. + +A bind is equal to _any_ value, literally anything! So, `@foo = 123` is always +true. + +> So, a bind is like a wildcard value? Then how does it actually "bind" anything +> to the local scope? + +Simple: Binds, when compared with a value, bind that value to the local scope. + +That means, when we evaluate this expression, the scope will have `foo` set to +`123`: + +```swift +@foo = 123 +``` + +--- + +You might have spotted something that hasn't been covered if you were reading +carefully: + +> Why the parenthesis around the `(@_)` bind, then? + +In order to prevent things from going out of control, Cab limits when binds can +bind the value they are compared to to their local scope. + +The rule that governs this is: A bind, when compared to a value, will bind that +value to the scope the bind was declared in _if the comparision operation is +within that scope_. + +So, we don't actually get `_` bound to `@Any` because the `=` is outside the +scope (aka parenthesis) of `@_`, and thus we don't litter in our standard +library. + +--- + +If you are even more keen, you must have noticed `,`, which is the "same" +operator. It evaluates both operands at the same time, letting us do neat stuff +like: + +```swift +@countinc = count + 1, +@count = 123, +``` + +Or even: + +```swift +@a = b, +@b = a, +``` + +This is similar to "toplevel" declarations in programming languages where you +can reference a declaration before it is declared in the source text. Basically +makes it evaluate in the order that it needs to, instead of top to down. + +Trailing `,` is a noop operator, `,` and `` are exactly the same. +Trailing commas are nice! + +

+ +```swift +@symbol = {[(2)]} @name & String => {[(3)]} { + {[(4)]} @`\(magic.name)` = name, +}, +``` + +

+ +I've already explained how comparisions & binds work in Cab, so I'll skip the +`@symbol =` part. + +The way lambdas work in Cab is as follows: ` => `. + +The `=>` is an infix operator, yet again. And the `` can be _any +expression_. + +When a lambda is called, the `` is compared with the argument in a new +scope. If they are not "equal", aka when the comparision evaluates to `false`, +an exception is thrown. + +But when it is `true`, the `` is evaluated and returned. + +Okay, as the way lambdas work in Cab is out of the way, let's see what this +expression is actually doing: + +In this lambda, `@name & String` is the ``, and { +@\`\\(magic.name)\` = name, } is the ``. + +## The value + +Let's start with the ``, we know what `@name` is, it's a bind and when +compared with the argument, it will set `name` in the local scope to it. + +What is the `& String` part? + +Answer: `&` is an infix operator, that takes two values and produces a value +that is **all of the given of the values, at the same time**. It's called the +`all` operator. + +What is `String`? It's a value that is equal to all strings. No, not exactly a +"type"! This is why Cab doesn't exactly have "typing", as everything is a value. + +This makes `@name & String` a value that is equal to any string, and when +compared to a string value, will bind it to the scope & have the comparision +expression evaluate to `true`. + +## The body + +Okay, so assuming the lambda was called properly, we should have a value named +`name` in our local scope. + +We will now evaluate this expression: + +```swift +{ + @`\(magic.name)` = name, +} +``` + +Let's break it down: + +`{}` is a special type of "parenthesis", it is identical to `()`, but unlike +`()`, when it evaluates the inner expression, it doesn't return the expression +itself. It returns its scope. + +So, `{ @foo = 123 }` will evaluate to an object of type `Attributes`, with `foo` +set to `123`. + +Let's focus on the inner expression: { @\`\\(magic.name)\` = name, +}, specifically the @\`\\(magic.name)\` part, as that is +new. + +It is a bind, but the identifier it has next to it isn't a "literal" identifier. +It's interpolated. + +`\(...)` is how you do string/identifier/path/etc interpolation in Cab, and it +takes a single expression within that has to evaluate to a string. And the way +you do "quoted" identifiers in Cab is by using \`. + +And the value within the bind is `magic.name`. `magic` is a builtin value, which +contains strings that are used by the runtime for magical stuff. + +The `.` operator is the "with scope" operator. The way `magic . name` is +evaluated is by setting the scope of `name` to `magic`, and evaluating `name`, +which is a reference. It's not an "attribute access" operator, because we can do +stuff like: `magic.[name, value]`. + +Here, we effectively get the `name` value within `magic` and use it for the +string interpolation.