Better Bee Be Better

“Spot more bugs than TS with less than JS”

On the menu

  • Motivation
  • “Less than JavaScript (JS)”
  • “Spot more bugs than TypeScript (TS)”
  • Future Plans

Motivation

I created a toy programming language (PL) for fun!

It’s a 1-year free-time project. I had too many ideas: read lots of papers, started by implementing a custom Garbage Collector (GC)…

But then, I significantly narrowed down the scope to make something usable, focusing more on syntax, semantics, and overall developer experience (like good error messages).

2 months ago: I started rewriting everything from scratch for a 0.1.0 release.

Check it out at https://bee-lang.org

A small (and thus, easy to learn) programming language that helps you write bug-free programs.

Bee looks-like JavaScript and targets JavaScript!

JS is BAD for Humans

JS has many well-known flaws:

  • Shallow-copy by default, which allows shared mutability;
  • Primitive type operations are as non-ergonomic as they can get;
  • Many operations fail silently by returning undefined instead of throwing errors, leading to unknown program states;
  • Different versions of slightly the same thing to fix historical bugs: function vs =>, == vs ===, Object vs Map, var vs let, class

JS is (Quite) GOOD for Machines

  • Cross-Platform, yet quite fast thanks to efficient runtimes with JITs;
  • There are small runtimes, making it easily embeddable in other programs.

But WASM (WebAssembly) is also fast and cross-platform.

It could even be considered a managed platform (now that it offer an optional GC to defer memory management to JS runtime).

Still, there are reasons to prefer JS over WASM:

  • Compatibility with older browsers;
  • Highly expressive with many reflective features;
  • Interoperability with Browser/Node APIs and existing JavaScript code;
  • Familiar and hackable with step-by-step debuggers;
  • Compilable to C via QuickJS! (kind of native backend for free).

What are the other PLs that target JavaScript?

CoffeeScript, TypeScript, PureScript, Elm, Haskell, ClojureScript, Reason, Kotlin/JS, F# (Fable), Scala.js, Opal, Haxe, Nim, GopherJS,…

…but Bee try to looks like JS!

A language smaller than JavaScript

E.g.:

  • There only one kind of function: lambda =>;
  • one kind of string literal: " ", that support interpolation;
  • etc.

In Bee, Everything Returns a Value, “It’s Like Lego Bricks”

Every statement or declaration could be assigned. There is no undefined, the empty object {} is used to represent the “empty” value, e.g., what console.log returns.

1{
2 // You can assign an `if` statement to an identifier
3 // (the `else` clause is mandatory)
4 x = if sunny "☀️" else "🌧️"
5}

A .bee file could contain arbitrary Bee expression.
It could be compiled to the matching .js, here a webpack loader call implicitly bee build:

1// greetings.bee
2name => console.log "Hello, ${name}!"


1// index.js
2import greetings from "./greetings.bee"
3greetings("World") // "Hello, World!"

JS (like most PLs), has so many different scope rules…

Disclaimer: all following code examples are annotated with errors spotted by tsc --strict, for a “fair” comparison 😉

1const x = greetings("🐝")
2function greetings(name: any) {
3 console.log(`Hello, ${name}!`)
4}
1const x = greetings("🐛")
2// ~~~~~~~~~
3// Error TS2448: Block-scoped variable 'greetings'
4// used before its declaration.
5const greetings = (name: any) => {
6 console.log(`Hello, ${name}!`)
7}
1const x = {
2 greetings: (name: any) => {
3 console.log(`Hello, ${name}!`)
4 },
5 a: greetings("🐛"),
6 // ~~~~~~~~~
7 // Error TS2304: Cannot find name 'greetings'.
8 b: x.greetings("🐛")
9 // ~
10 // Error TS2448: Block-scoped variable 'x' used
11 // before its declaration.
12 }
1const boom: any = () => {
2 console.log(x) // x = "💥"
3 boom()
4}
5const x = "💥";
6boom() // Runtime JS error ...
7// RangeError: Maximum call stack size exceeded

In Bee, “Object” and “Block” are one same thing: a Block could be assigned, and so used as Object literal:

1{
2 foo = {
3 bar = 42
4 console.log(bar) // side-effects within blocks
5 bar = 43 // shadow (redefine) values
6 }
7 console.log(foo.bar) // 43
8}

Bee is Pure!

e.g., in JS you have shared mutability:

1const unsafe = () => { x[0] += 1 }
2
3const x = [42]
4unsafe()
5console.log(x) // [43]
6
7// ... But, TypeScript has `readonly`?!

In Bee, the invariant “an identifier can have its associated value changed only within its scope” always hold:

1{
2 // ...
3
4 x = [42]
5 whatever(x)
6 assert(x == [42]) // ... will always be true in Bee!
7}

Immutability is implemented using structural sharing:

1{
2 x = 1
3 x = 2 // Shadowing of `x`
4 {
5 x = 3 // CoW (Copy-on-Write) of `x`
6 }
7 console.log(x) // `x` is still equal to 2
8}

Spot more bugs than TypeScript

TypeScript inherits the same flaws as JS, e.g., shared mutability!

Bad Inference…

1// Example borrowed from the TypeScript tutorial:
2declare function pad(s: string,
3 n: number,
4 direction: "left" | "right"): string
5pad("hi", 10, "left")
6let s = "right"
7pad("hi", 10, s)
8// ~
9// error TS2345: Argument of type 'string' is not
10// assignable to parameter of type '"left" | "right"'.

Bad Typing…

1function f(x: string | number) {
2 function unsafe() { x = 42 }
3 if (typeof x === 'string') {
4 unsafe()
5 // Runtime JS error ...
6 console.log(x.toUpperCase())
7 // ~~~~~~~~~~~
8 // TypeError: x.toUpperCase is not a function
9 }
10}
11let input: string | number = 'Hello'
12f(input)

How Types (Will) Work in Bee

Types as Values

Types are treated like special functions that can be composed. E.g.:

1{
2 // Types are just fancy functions ...
3 type Positive = x => x > 0
4 // Structural Typing
5 type User = { email = Email, ... }
6 // Union Types
7 type Flowers = "🌺" | "🌼" | "🌸"
8 // Intersection Types
9 type PositiveInt = BigInt & Positive
10}

Abstract Interpretation

Checking a program is like running a program, but:

  • with a different standard library, where no side effects are possible;
  • where branching and function calls have different semantics.

e.g., random.int 0..5 simply returns Range 0 5 (representing a BigInt value between 0 and 5).

Bee Has Great Error Messages!

DEMO ✨

What Has Been Implemented…

  • Funny Bugs! 🐛
  • Syntax grammar.js with TreeSitter;
  • Semantics (destructuring, spread operator, implicit function arguments, operator overloading, etc.);
  • Prelude (minimal standard library);
  • CLI (that works without node in your PATH);
  • Build targets (e.g., @debug, @release or user-defined);
  • Build cache (file-based);
  • Basic editor support (syntax highlighting);

What’s Left to Do…

  • Pattern matching (in lambda definition, let-bindings and branches of a match keyword);
  • Constrained mutability (e.g. needed for while loops);
  • Subtyping (keeping track of the partial order of Types declared within a scope).

Looking for Grants or Sponsorship

To implement…

  • A Language Server Protocol (LSP) and Debug Adapter Protocol (DAP);
  • A native backend (C, WASM or LLVM);
  • An incremental typing algorithm.

🌺
Thanks to:
🐞 Adrien, 🦋 Hecate, 🪲 Nao and 🐜 Xavier.

🌼
Follow the project on mailing-list, and https://functional.cafe/@yvan

🌸