Lea for JavaScript Developers
If you know JavaScript, you already know more Lea than you think! This guide maps JavaScript concepts to their Lea equivalents.
Quick Reference
| JavaScript | Lea |
|---|---|
const x = 5 | let x = 5 |
let y = 5 | maybe y = 5 |
x => x * 2 | (x) -> x * 2 |
[1,2,3].map(x => x * 2) | [1,2,3] /> map((x) -> x * 2) |
console.log(x) | x /> print |
// comment | -- comment |
{ name: "Alice" } | { name: "Alice" } |
obj.property | obj.property |
await fetch(url) | await fetch(url) |
Variables
// JavaScript
const name = "Alice"; // immutable binding
let counter = 0; // mutable binding
counter = 1; // reassignment
-- Lea
let name = "Alice" -- immutable binding
maybe counter = 0 -- mutable binding
counter = 1 -- reassignment
Note: Lea uses let for immutable (like JS const) and maybe for mutable (like JS let).
Functions
// JavaScript
const double = x => x * 2;
const add = (a, b) => a + b;
const greet = (name = "World") => `Hello ${name}!`;
-- Lea
let double = (x) -> x * 2
let add = (a, b) -> a + b
let greet = (name = "World") -> `Hello {name}!`
Key differences:
- Arrow syntax:
->instead of=> - Template strings use
{expr}instead of${expr} - Parentheses always required around parameters
Multi-line Functions
// JavaScript
const process = (x) => {
const y = x * 2;
const z = y + 1;
return z;
};
-- Lea (indentation-based)
let process = (x) ->
let y = x * 2
let z = y + 1
z
-- Or with explicit return
let process = (x) ->
let y = x * 2
y > 100 ? return 100 : 0
y + 1
The Pipe Operator
This is the biggest paradigm shift! Instead of nested function calls or method chaining, Lea uses pipes:
// JavaScript - nested calls
console.log(Math.sqrt(Math.abs(-16)));
// JavaScript - method chaining (where available)
[-16].map(Math.abs).map(Math.sqrt).forEach(console.log);
-- Lea - pipes (read left to right)
-16 /> abs /> sqrt /> print
Passing Arguments Through Pipes
// JavaScript
const add = (a, b) => a + b;
add(5, 3); // 8
-- Lea - piped value becomes first argument
5 /> add(3) -- becomes add(5, 3) = 8
-- Or use 'input' placeholder for different position
5 /> add(3, input) -- becomes add(3, 5) = 8
Array Methods
// JavaScript
const nums = [1, 2, 3, 4, 5];
nums.map(x => x * 2); // [2, 4, 6, 8, 10]
nums.filter(x => x > 2); // [3, 4, 5]
nums.reduce((acc, x) => acc + x, 0); // 15
-- Lea (same operations, piped)
let nums = [1, 2, 3, 4, 5]
nums /> map((x) -> x * 2) -- [2, 4, 6, 8, 10]
nums /> filter((x) -> x > 2) -- [3, 4, 5]
nums /> reduce(0, (acc, x) -> acc + x) -- 15
Note: In Lea's reduce, the initial value comes first!
Chaining Operations
// JavaScript
[1, 2, 3, 4, 5]
.filter(x => x > 2)
.map(x => x * 2)
.reduce((acc, x) => acc + x, 0);
-- Lea
[1, 2, 3, 4, 5]
/> filter((x) -> x > 2)
/> map((x) -> x * 2)
/> reduce(0, (acc, x) -> acc + x)
Index Access in Callbacks
// JavaScript
['a', 'b', 'c'].map((x, i) => `${i}: ${x}`);
// ['0: a', '1: b', '2: c']
-- Lea
["a", "b", "c"] /> map((x, i) -> `{i}: {x}`)
-- ["0: a", "1: b", "2: c"]
Objects (Records)
// JavaScript
const user = { name: "Alice", age: 30 };
user.name; // "Alice"
// Destructuring
const { name, age } = user;
// Spread
const updated = { ...user, age: 31 };
-- Lea (nearly identical!)
let user = { name: "Alice", age: 30 }
user.name -- "Alice"
-- Destructuring
let { name, age } = user
-- Spread
let updated = { ...user, age: 31 }
Conditionals
// JavaScript ternary
const result = x > 0 ? "positive" : "non-positive";
// JavaScript if/else (statement, not expression)
let result;
if (x > 0) {
result = "positive";
} else {
result = "non-positive";
}
-- Lea (ternary only - everything is an expression)
let result = x > 0 ? "positive" : "non-positive"
-- Pattern matching for complex cases
let result = match x
| if input > 0 -> "positive"
| if input < 0 -> "negative"
| "zero"
Async/Await
// JavaScript
async function fetchData(url) {
const response = await fetch(url);
return response.json();
}
const data = await fetchData("https://api.example.com");
-- Lea
let fetchData = (url) -> fetch(url) #async
let data = await fetchData("https://api.example.com")
Promise.all Equivalent
// JavaScript
const results = await Promise.all([
fetch(url1),
fetch(url2),
fetch(url3)
]);
-- Lea
let results = [url1, url2, url3] /> parallel((url) -> fetch(url))
Spread Operator
// JavaScript
const a = [1, 2];
const b = [3, 4];
const combined = [...a, ...b]; // [1, 2, 3, 4]
-- Lea (identical!)
let a = [1, 2]
let b = [3, 4]
let combined = [...a, ...b] -- [1, 2, 3, 4]
String Operations
// JavaScript
const greeting = `Hello ${name}!`;
const combined = "Hello" + " " + "World";
-- Lea
let greeting = `Hello {name}!` -- template string
let combined = "Hello" ++ " " ++ "World" -- ++ for concat
Note: Lea uses ++ for string concatenation, not +.
Decorators
Lea has built-in decorators (JavaScript has proposed decorators, but Lea's are simpler):
// JavaScript (proposed/experimental)
@memoize
function fib(n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
-- Lea (trailing decorators)
let fib = (n) -> n <= 1 ? n : fib(n - 1) + fib(n - 2) #memo
Available decorators:
#log- log inputs/outputs#memo- memoization#time- timing#retry(n)- retry on failure#validate- runtime type checking
Type Annotations
// TypeScript
function add(a: number, b: number): number {
return a + b;
}
-- Lea (trailing type syntax)
let add = (a, b) -> a + b :: (Int, Int) :> Int
What Lea Doesn't Have
class- use records and functionsthis- use closures or context systemfor/whileloops - usemap,filter,reduce, recursionnull/undefineddistinction - justnull===vs==- just==- Semicolons - not needed
var- doesn't exist
Lea-Specific Features
Parallel Pipes
-- Fan out to multiple functions, fan in to combine
10 \> addOne \> double /> combine
-- Runs addOne(10) and double(10) concurrently
First-Class Pipelines
-- Store a pipeline as a value
let process = /> filter((x) -> x > 0) /> map((x) -> x * 2)
-- Apply it
[1, -2, 3] /> process -- [2, 6]
Reversible Functions
-- Define forward and reverse
let double = (x) -> x * 2
and double = (x) <- x / 2
5 /> double -- 10 (forward)
10 </ double -- 5 (reverse)
Context System (like React Context)
context Logger = { log: (msg) -> print(msg) }
let greet = (name) ->
@Logger
Logger.log("Hello " ++ name)
Migration Tips
- Think in pipes: Instead of
f(g(h(x))), writex /> h /> g /> f - Everything is an expression: No statements, everything returns a value
- Immutable by default: Use
letfor most things,maybeonly when needed - Use pattern matching: Instead of if/else chains, use
match - Leverage decorators:
#memo,#log,#timeare your friends
Happy coding!