Loon Programming Language

repository·main·Indexed 40 days ago

https://github.com/ecto/loon

A functional programming language featuring invisible types via Hindley-Milner inference, safe ownership with move semantics, and an algebraic effect system. Loon supports multi-shot continuations, handler towers for environment switching, and a codegen pipeline targeting a Register VM, WASM, and Cranelift.

Tokens
62.2K
Snippets
205
Records
324
Agent score
50%

What's inside Loon

  1. Understand Truthiness and Null Safety

    main

    Loon does not have null, nil, or undefined. Instead, it uses Option and Result types to handle absence and errors.

    Truthiness Rules

    A value is truthy unless it 'says no' (false) or 'says nothing' ((), None).

    Falsy values:

    • false
    • () (unit)
    • None

    Truthy values include:

    • 0 (integer)
    • 0.0 (float)
    • "" (empty string)
    • #[] (empty collection)
    • [Some x] (any Some wrapper, even if x is false)

    Pattern: Unwrapping Values

    To safely unwrap Option values while testing truthiness, use:

    • [if-let [x expr] then else]
    • [when-let [x expr] body…]
    • Predicates some? and none? for exact complement testing.
  2. Understand Ownership and Borrowing

    main

    Loon uses an ownership model similar to Rust, but relies on aggressive inference so you rarely write annotations.

    Core Rules

    1. Every value has exactly one owner.
    2. When the owner goes out of scope, the value is dropped.
    3. Values can be moved (ownership transferred) or borrowed (temporary access).
    4. At any time, you can have either one mutable reference OR any number of immutable references, but never both.

    Automatic Inference

    • Borrowing: If a function only reads a parameter, the compiler automatically infers a borrow.
    • Moving: If a function stores a parameter in a struct or returns it, the compiler automatically infers a move.
    • Lifetimes: Lifetimes are tracked and inferred internally; you do not need to write 'a annotations unless inference is ambiguous.

    Copy Types

    Primitives (i32, f64, Bool, etc.) implement Copy and are duplicated rather than moved. You can opt-in small structs to Copy using [#[derive Copy]].

    ; Compiler infers borrow (read-only)
    [defn greet [name]
      [str "hello, " name]]
    
    [let name "alice"]
    [greet name]
    [println name] ; Valid because name was only borrowed
    
    ; Compiler infers mutation (mutable borrow)
    [defn add-world [s]
      [push! s " world"]]
    
    ; Compiler infers move (ownership transfer)
    [defn take [s]
      [store-in-db s]]
    
    ; Opt-in to Copy behavior
    [#[derive Copy]
     type Point [x f64] [y f64]]
  3. Define Kernel Constraints

    main

    Loon kernels are highly restricted to ensure safety and portability. A kernel cannot perform any operations that require a handler tower (i.e., it cannot perform side effects).

    Prohibited in kernels:

    • Closures
    • Memory allocation
    • String manipulation
    • Effects (e.g., IO.println)

    If you attempt to use an effect inside a kernel, the compiler will error with a message similar to: kernel 'k' contains the effect operation 'IO.println' why: a kernel runs where there is no handler tower to perform effects against.

  4. Verify loon-hdl board (PCB) readiness

    main

    The PCB design must pass several checks before gerber export:

    • Connectivity: The schematic and netlist must be correct (e.g., 27 nets including QSPI weight, SPI-IMU, camera, motors, and power).
    • BOM: The Bill of Materials must resolve to real Manufacturer Part Numbers (MPNs) with valid IPC footprints.
    • Physical Layout: The board is sized at 64×64 mm to fit the drone center plate.
    • Critical Gates:
      • Routing: All nets must be routed.
      • DRC: The Design Rule Check must be clean.
      • Placement: Ensure no component overlaps (e.g., check J2/C5).
    • Export Workflow: Once routing and DRC are complete, use export_gerber to produce the necessary manufacturing files.
  5. Understand loon's core features

    main

    Loon is a functional language designed around the following principles:

    • Invisible Types: Uses full Hindley-Milner type inference with let-polymorphism and row types. No manual annotations are required.
    • Safe Ownership: Implements Rust-style move semantics and borrow checking inferred from dataflow, without requiring explicit lifetimes.
    • Algebraic Effects: Replaces exceptions, async, and mutable state with a system of declaring, performing, and handling effects.
    • Multi-shot Continuations: Handlers can resume a captured continuation zero, one, or many times, enabling nondeterministic search and backtracking.
    • Handler Towers: Allows the same program to run under different handlers (e.g., switching between deterministic offline tests and real production IO) without changing the code.
    • Async as Effects: Concurrency is handled via the scheduler as an effect, using cooperative spawn/yield/cancel with structured concurrency and no function coloring.
    • Pipes: Enables data transformation chains using pipe syntax to avoid nesting and temporary variables.
  6. Use Persistent Vectors and Maps

    main

    Persistent collections are immutable and use structural sharing.

    Vectors (#[]) use conj to add elements, returning a new vector without modifying the original.

    Maps ({}) guarantee insertion order.

    • assoc of an existing key updates the value in place (position is preserved).
    • assoc of a new key appends it to the end.
    • merge is left-biased: keys in the left map keep their position and value; new keys from the right map are appended.

    Note: On the WASM codegen backend, structural equality (=) for maps/vectors is currently implemented by reference rather than value.

    ; Persistent Vectors
    [let v #[1 2 3 4 5]]
    [let v2 [conj v 6]]         ; #[1 2 3 4 5 6] — v is unchanged
    
    ; Persistent Maps
    [let m {:name "loon" :version "0.1" :cool true}]
    [get m :name]                ; "loon"
    [let m2 [assoc m :version "0.2"]]
    
    ; Left-biased merge
    [merge {:a 1 :b 2} {:b 9 :c 3}] ; {:a 1 :b 2 :c 3}
  7. Understand the Loon Form (AST) structure

    main

    The Form is the single shared surface type (Abstract Syntax Tree) used across all stages of the Loon compiler. It represents the fundamental building blocks of the language. Every node in a Form carries a span (source location) for diagnostics.

    Supported Form types include:

    • FInt, FFloat, FStr (Literals)
    • FKw (Keywords)
    • FSym (Symbols - these carry a scope-set slot for hygiene)
    • FList, FVec, FSet, FMap, FTuple (Collections)
    • FQuote, FUnquote, FSplice (Quasiquoting)
    • FDot, FQuery, FError (Other constructs)

    Note: Numeric literals in the Form keep their original lexeme text; the reader does not interpret them into numbers.

  8. Loon Syntax Overview

    main

    Loon uses a LISP-style syntax with specific delimiters to improve readability and scanning:

    • [] (Square brackets): The primary delimiter for s-expressions.
    • () (Parentheses): Used for grouping expressions or defining tuples.
    • {} (Curly braces): Used for maps and records.
    • #[]: Used for arrays/collections.

    Loon is maximally homoiconic, meaning the source code structure directly represents the Abstract Syntax Tree (AST).

    [defn greet [name]
      [str "hello, " name "!"]]
    
    [defn greet-all [names]
      [pipe names
        [map greet]
        [collect]]]
    
    [defn main []
      [let users #["alice" "bob" "carol"]]
      [pipe [greet-all users]
        [each println]]]
  9. How effect-row representation works

    main

    Loon uses a row-based representation for effect inference, which is structurally analogous to how type-level rows work for records. This allows for effect polymorphism (e.g., a compose function whose effect row is inferred from its arguments).

    Key Components:

    • EffectRow: Consists of a BTreeSet<String> of concrete effect labels and an optional tail (Option<TypeVar>).
    • Closed vs. Open Rows: A None tail represents a closed row; Some(TypeVar) represents an open row.
    • Inference Mechanism: The checker performs row unification. It pushes labels through the ambient row's tail, links callee rows into the ambient row during calls, and uses handle to subtract handled labels and constrain the body's tail to prevent leaks.
    • Errors: Row mismatches result in error code E0403.
    pub struct EffectRow {
        pub labels: BTreeSet<String>,   // concrete effect labels
        pub tail: Option<TypeVar>,      // None = closed, Some = open
    }
  10. Verify loon-hdl frame (CAD) manufacturability

    main

    The drone frame is designed for FDM (Fused Deposition Modeling) manufacturing.

    • DFM Status: The design is considered DFM-ready if it has 0 errors (warnings regarding downward faces sitting on the build plate are acceptable as they print in natural orientation).
    • Physical Specs: Target mass is approximately 35–55 g (using nylon with typical infill) for a 26 cm quad.
    • Critical Gate: The Center of Mass (CoM) must be centered. An off-center CoM (currently reading +27 mm x due to boolean union artifacts) will cause uncontrollable trim during flight. Resolve this by modeling arms as distinct parts before running inspect_cad and export_cad.
  11. Define and call type methods

    main

    Methods can be defined directly inside type variant declarations. Loon automatically generates dispatch functions for these methods.

    When calling a method, the parameters of the method correspond to the fields of the specific variant. Loon automatically destructures the ADT and binds the variant's fields to the method's parameters before evaluation.

    Types are open by default; you can extend a type by declaring it again with new variants.

    [type Shape
      [Circle f64
        [fn area [r] [* 3.14159 r r]]
        [fn describe [r] "circle r={r}"]]
      [Rect f64 f64
        [fn area [w h] [* w h]]
        [fn describe [w h] "{w}x{h}"]]
      Point
        [fn area [] 0.0]
        [fn describe [] "point"]]
    
    [area [Circle 5.0]]        ; Returns 78.539...
    [describe [Rect 3.0 4.0]]  ; Returns "3x4"
  12. Use Pattern Matching and Destructuring

    main

    Loon provides powerful pattern matching via match.

    Key Rules:

    • Match Patterns: A pattern like #[a b] matches a vector of exactly length 2.
    • Destructuring (let): Unlike match, destructuring with let requires at least as many elements as there are binders. If the sequence is too short, it triggers a loud runtime error.
    • Guards: Use [when <condition>] within a match arm to add logic.
    • Wildcard: Use _ to match anything.
    ; Pattern matching with guards
    [match n
      0             => "zero"
      n [when [> n 0]] => "positive"
      _             => "negative"]
    
    ; Destructuring
    [let [x y] [get-point]]
    [let {name age} user]
    
    ; Vector patterns in match (exact length)
    [match v
      #[a b]   => [+ a b]
      _        => "other"]