Overview

The Boomla language was designed for the Boomla platform. It’s primary focus is on simplifying web application development.

Notable features

 

Hello world

Let’s see a hello-world example to get a feel of the language.

 

Persistent storage

Boomla supports storing any value on disk, without manual serialization / deserialization.

To allow addressing those values, we have a special type constructor file{} which stores a set of fields (like struct{}) and also a file ID when stored on disk, which is used for transparently referencing those values.

A file{} is still a value, just like a number is a value. You can assign it to a variable and pass it around. When storing a file{}, you get back a reference to the stored value, which has type $file{}. Any type in the form $T is a reference to a stored value of type T. To rephrase, $ stands for Stored.

In the above example, we don’t specify where the value should be stored, so a new temporary filesystem is created with the root file being the one we just stored. (That’s the best we can do in this playground.)

Reading and writing stored values has the same syntax as reading and writing non-stored values.

 

JSX like syntax

Boomla has native support for generating HTML5 views. It also has native Turbo CSS integration, which is the design language of Boomla.

Unlike in JSX, you can embed code blocks inside tags.

In the docs, you will find it under Xml expressions.

 

Error handling

We really like how the explicit error handling of Go works. It is extremely verbose though. Alternatives like options are equally verbose.

Instead, Boomla splits a function’s results into success results and optionally a failure result. Use return for success results, fail for failure results.

    // Success result only
    fn sum(a, b int) (sum int)
    // Success result, failure result
    fn sum(a, b int) (sum int) (err error)
    // Multiple success results, failure result
    fn swap(a, b int) (c, d int) (err error)

At call sites, the failure result must be handled explicitely. The verbose way is to capture the error in a variable and then do something with it, for example, return it as the failure result of the current function.

There is also a shorthand for the above: simply add a question mark (?) after the call.

 

Mutable Value Semantics

Semantics roughly means behaviour. There is value semantics and reference semantics.

Reference semantics is about having multiple references to the same underlying value. Let’s just call them pointers. Explicit pointers are hard to understand so some high level languages like JavaScript hide them by having certain types that behave as pointers (Objects, Arrays), while the rest behaves as values.

Unfortunately, pointers are fundamentally flawed. For one, they are a common source of bugs. That’s bad. But there is worse.

Pointers encourage tight coupling in your programs.

Pointers encourage tight architectural coupling via parent pointers. You create a Comment type and suddenly you can’t have a Comment object without having it belong to a Post that belongs to a Category that belongs to a Website.

Attaching the hidden complexity of parent objects makes it really hard to fully understand and reason about a program.

Value semantics is about value independence. That changing one variable won’t change another. To rephrase, two variables can’t have shared mutable state.

Avoiding shared mutable state can be done in two ways. You can disallow the shared part or the mutable part. Functional programming languages go after the mutable part by completely disallowing mutations, using copy-on-write data structures. Boomla disallows sharing instead, which has better performance and ergonomics.

Example

Independent values a and b:

Example

Inout argument ~a allows mutating the original variable.

Note:

  • Stored values are references. They can’t have circular references though, so they are a simpler form of references.
  • You should design your programs around value types, not reference types, where possible.
  • Reference types are a smaller issue in simple (shallow) programs. Don’t worry too much about it there. They become an issue in bigger (deep) programs. That’s where you should avoid using them.

 

File oriented programming

File oriented programming is a concept where files are used to structure your codebase.

Note: these are not the files you know. The entire concept of what a file is needs to be redefined.

  • Files must have the ability to contain files and lists of files.
  • Files must be statically typed. They must define what files they may contain.
  • Files are a primary means of composition: a parent may call methods on its children.

Imagine you are building a one-off wrapper component for a web application. It will render a border around whatever it displays. Let’s just say it will display “hello world”.

In a typical web framework, you would create some kind of a Wrapper component, then wrap the child component inside it. Like so:

<Wrapper>hello world</Wrapper>

There are several problems with this:

  • You have to name your wrapper component, which is one of the hardest things in programming.
  • The Wrapper and the inner component are logically in a parent-child relationship, yet their sources are not. This creates disorder. Encapsulation is one of the most fundamental tools to utilize as a developer.
  • You have to write the Wrapper and the inner component using the same programming language.

With the file-oriented approach of Boomla:

  • Both the wrapper component and the inner component are standalone files.
  • The wrapper component’s file contains the inner component’s file.

The file-oriented approach uses file-level encapsulation to simplify your codebase.

The wrapper file could be a Boomla source file and contain:

var child file interface{
    Render(doc live.View) () (err error)
}
fn Render(doc live.View) () (err error) {
    <div turbo="t1 b-4-red p-16">
        {{
            child.Render(doc)?
        }}
    </div>
}

The child file could be a markdown source file and contain:

hello world

Notice:

  • You didn’t have to name the parent and the child component.
  • The parent and the child components are loosely coupled.
  • They are independent, composible building blocks. You could easily replace the child and the wrapper components simply by moving files.