Language Tour

Learn lykn in 10 Examples

Each example shows the lykn source and its compiled JavaScript output.
No runtime, no magic — just parentheses in, clean JS out.

1 · Bindings

Hello, World

bind creates an immutable binding. It compiles to const — always. Colon syntax (console:log) compiles to dot notation.

lykn

(bind greeting "Hello, World!")
(console:log greeting)

javascript

const greeting = "Hello, World!";
console.log(greeting);

2 · Functions

Named Functions with Contracts

func defines named functions with keyword-labeled clauses. Type annotations compile to runtime checks in development, stripped in production.

lykn

(func greet
  :args (:string name)
  :returns :string
  :body
  (template "Hello, " name "!"))

(console:log (greet "world"))

javascript

function greet(name) {
  if (typeof name !== "string")
    throw new TypeError(/* ... */);
  const result = `Hello, ${name}!`;
  return result;
}
console.log(greet("world"));

3 · Lambdas

Anonymous Functions

fn creates arrow functions. Type annotations work the same way. Use them everywhere a callback is needed.

lykn

(bind numbers #a(1 2 3 4 5))

(bind doubled
  (numbers:map
    (fn (:number x) (* x 2))))

(console:log doubled)
;; → [2, 4, 6, 8, 10]

javascript

const numbers = [1, 2, 3, 4, 5];

const doubled =
  numbers.map(
    (x) => x * 2);

console.log(doubled);
// → [2, 4, 6, 8, 10]

4 · Closures

Functions Remember Their Context

Because bind is immutable, closures are safe. The captured value never changes behind the function's back.

lykn

(func make-greeter
  :args (:string prefix)
  :returns :function
  :body
  (fn (:string name)
    (template prefix ", " name "!")))

(bind hello (make-greeter "Hello"))
(console:log (hello "World"))
;; → "Hello, World!"

javascript

function makeGreeter(prefix) {
  return (name) =>
    `${prefix}, ${name}!`;
}

const hello = makeGreeter("Hello");
console.log(hello("World"));
// → "Hello, World!"

5 · Types

Algebraic Data Types

type defines variants with named, typed fields. Each variant compiles to a constructor function returning a tagged object. Zero-field variants are constants.

lykn

(type Shape
  (Circle :number radius)
  (Rect   :number w :number h)
  (Point))

(bind s (Circle 5))
;; → { tag: "Circle", radius: 5 }

javascript

function Circle(radius) {
  /* type check in dev */
  return { tag: "Circle", radius };
}
function Rect(w, h) {
  return { tag: "Rect", w, h };
}
const Point = { tag: "Point" };

const s = Circle(5);

6 · Pattern Matching

Exhaustive match

The compiler ensures every variant is handled. Add a variant to Shape and forget to update the match? Compile error — not a runtime surprise.

lykn

(func area
  :args (:any s)
  :returns :number
  :body
  (match s
    ((Circle r)  (* Math:PI r r))
    ((Rect w h)  (* w h))
    ((Point)     0)))

javascript

function area(s) {
  if (s.tag === "Circle") {
    const r = s.radius;
    return Math.PI * r * r;
  }
  if (s.tag === "Rect") {
    return s.w * s.h;
  }
  return 0;
}

7 · Mutation

Cells — Controlled Mutable State

Mutation is explicit and visible. cell creates a mutable container. swap! updates it. express reads it. Every mutation wears a ! suffix.

lykn

(bind count (cell 0))

(swap! count
  (fn (:number n) (+ n 1)))

(console:log (express count))
;; → 1

javascript

const count = { value: 0 };

count.value =
  ((n) => n + 1)(count.value);

console.log(count.value);
// → 1

8 · Threading

Data Pipelines

Threading macros turn nested calls inside-out. -> threads as the first argument; ->> threads as the last. some-> short-circuits on null.

lykn

(bind result
  (->> #a(1 2 3 4 5 6 7 8 9 10)
    (:filter (fn (:number n)
                   (= (% n 2) 0)))
    (:map (fn (:number n)
                 (* n 10)))
    (:reduce + 0)))

javascript

const result =
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    .filter((n) => n % 2 === 0)
    .map((n) => n * 10)
    .reduce(
      (a, b) => a + b, 0);
// → 300

9 · Modules

ES Modules

Standard ESM. Named imports only — no namespace imports, no CommonJS. Explicit dependencies, always.

lykn

(import "./math.js" (add multiply))
(export
  (func calculate
    :args (:number x :number y)
    :returns :number
    :body (add (multiply x y) x)))

javascript

import { add, multiply } from "./math.js";
export function calculate(x, y) {
  return add(multiply(x, y), x);
}

10 · All Together

Option and Result

Lykn provides Option and Result types for safe error handling. No null checks, no thrown exceptions for expected failures — just data and pattern matching.

lykn

(type Option
  (Some :any value)
  None)

(func find-user
  :args (:number id)
  :returns :any
  :body
  (if (= id 1)
    (Some (obj :name "Duncan"))
    None))

(match (find-user 1)
  ((Some user)
    (console:log user:name))
  (None
    (console:log "not found")))

javascript

function Some(value) {
  return { tag: "Some", value };
}
const None = { tag: "None" };

function findUser(id) {
  if (id === 1)
    return Some({ name: "Duncan" });
  return None;
}
const t = findUser(1);
if (t.tag === "Some")
  console.log(t.value.name);
else
  console.log("not found");

Ready to Build?

Install lykn and start writing. The full book covers everything from here through macros, the browser, servers, and the compiler internals.

$ cargo install lykn-cli