1
Fork 0
mirror of https://github.com/RGBCube/Site synced 2025-07-30 20:47:46 +00:00

blog(intro-cab): unlisted draft

This commit is contained in:
RGBCube 2025-06-20 15:33:54 +03:00
parent 944cebf51a
commit 5e66ce06ce
Signed by: RGBCube
SSH key fingerprint: SHA256:CzqbPcfwt+GxFYNnFVCqoN5Itn4YFrshg1TrnACpA5M

View file

@ -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:
<h1>
```swift
@Any = (@_), {[(1)]}
```
</h2>
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 `@<identifier-here>` 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, `<expr>,` and `<expr>` are exactly the same.
Trailing commas are nice!
<h1>
```swift
@symbol = {[(2)]} @name & String => {[(3)]} {
{[(4)]} @`\(magic.name)` = name,
},
```
</h1>
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: `<value> => <body>`.
The `=>` is an infix operator, yet again. And the `<value>` can be _any
expression_.
When a lambda is called, the `<value>` 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 `<body>` 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 `<value>`, and <code>{
@\`\\(magic.name)\` = name, }</code> is the `<body>`.
## The value
Let's start with the `<value>`, 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: <code>{ @\`\\(magic.name)\` = name,
}</code>, specifically the <code>@\`\\(magic.name)\`</code> 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 <code>\`</code>.
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.