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:
parent
944cebf51a
commit
5e66ce06ce
1 changed files with 220 additions and 0 deletions
220
site/blog/2025-06-20-intro-cab.md
Normal file
220
site/blog/2025-06-20-intro-cab.md
Normal 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.
|
Loading…
Add table
Add a link
Reference in a new issue