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