“Spot more bugs than TS with less than JS”
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 has many well-known flaws:
undefined
instead of throwing errors, leading to unknown program states;function
vs =>
, ==
vs
===
, Object
vs Map
,
var
vs let
, class
…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:
CoffeeScript, TypeScript, PureScript,
Elm, Haskell,
ClojureScript, Reason,
Kotlin/JS, F# (Fable), Scala.js, Opal,
Haxe, Nim, GopherJS,…
…but Bee try to looks like
JS!
E.g.:
=>
;" "
, that support
interpolation;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 |
2 | name => console.log "Hello, ${name}!" |
1 | // index.js |
2 | import greetings from "./greetings.bee" |
3 | greetings("World") // "Hello, World!" |
Disclaimer: all following code examples are
annotated with errors spotted by tsc --strict
, for a
“fair” comparison 😉
1 | const x = greetings("🐝") |
2 | function greetings(name: any) { |
3 | console.log(`Hello, ${name}!`) |
4 | } |
1 | const x = greetings("🐛") |
2 | // ~~~~~~~~~ |
3 | // Error TS2448: Block-scoped variable 'greetings' |
4 | // used before its declaration. |
5 | const greetings = (name: any) => { |
6 | console.log(`Hello, ${name}!`) |
7 | } |
1 | const 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 | } |
1 | const boom: any = () => { |
2 | console.log(x) // x = "💥" |
3 | boom() |
4 | } |
5 | const x = "💥"; |
6 | boom() // 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 | } |
e.g., in JS you have shared mutability:
1 | const unsafe = () => { x[0] += 1 } |
2 | |
3 | const x = [42] |
4 | unsafe() |
5 | console.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 | } |
TypeScript inherits the same flaws as JS, e.g., shared mutability!
1 | // Example borrowed from the TypeScript tutorial: |
2 | declare function pad(s: string, |
3 | n: number, |
4 | direction: "left" | "right"): string |
5 | pad("hi", 10, "left") |
6 | let s = "right" |
7 | pad("hi", 10, s) |
8 | // ~ |
9 | // error TS2345: Argument of type 'string' is not |
10 | // assignable to parameter of type '"left" | "right"'. |
1 | function 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 | } |
11 | let input: string | number = 'Hello' |
12 | f(input) |
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 | } |
Checking a program is like running a program, but:
e.g., random.int 0..5
simply returns
Range 0 5
(representing a BigInt
value between
0
and 5
).
DEMO ✨
grammar.js
with
TreeSitter;node
in your
PATH
);@debug
,
@release
or user-defined);match
keyword);while
loops);To implement…
🌺
Thanks to:
🐞 Adrien, 🦋 Hecate, 🪲 Nao and 🐜 Xavier.
🌼
Follow the project on announce@bee-lang.org mailing-list, and https://functional.cafe/@yvan
🌸