Skip to content

Instantly share code, notes, and snippets.

@prescod
Last active February 16, 2026 22:09
Show Gist options
  • Select an option

  • Save prescod/40a3972aeedda72f3dfb7c06cfcbb4f9 to your computer and use it in GitHub Desktop.

Select an option

Save prescod/40a3972aeedda72f3dfb7c06cfcbb4f9 to your computer and use it in GitHub Desktop.

BechML Reference Manual

A quick-reference guide for AI systems learning BechML - a friendly, higher-kinded, modular, functional scripting language.


Overview

BechML is a statically-typed functional programming language with:

  • Higher-kinded types
  • Algebraic data types (sum and product types)
  • Pattern matching
  • Module system
  • Type classes (Functor, Applicative, Monad, Monoid)
  • Lambda expressions
  • Polymorphic/generic functions

Basic Syntax

Statements

All top-level declarations end with semicolons:

name : Type := expression;

Comments

-- Single line comment (Haskell-style, inferred from language family)

Type Annotations

Types are declared with : before the assignment :=:

functionName : TypeSignature := implementation;

Primitive Types

Type Description
int Integer numbers
bool Boolean values
string Text strings
() Unit type (void)

Type Constructors

Syntax Meaning
[a] List of type a
a -> b Function from a to b
t a Type t applied to type a
t a b Type t applied to types a and b

Generic Types (Polymorphism)

Type variables are declared in angle brackets before the type signature:

-- Single type variable
id : <a> a -> a := \a -> a;

-- Multiple type variables
const : <a b> a -> b -> a := \a _ -> a;

-- In function types
map : <a b> (a -> b) -> [a] -> [b] := #list_map;

Lambda Expressions

Lambdas use backslash \ syntax:

-- Single parameter
\x -> x

-- Multiple parameters
\a b -> expression

-- With ignored parameter (underscore)
\_ -> value

-- Nested/curried
\f -> \x -> f x

Examples

id : <a> a -> a := \a -> a;
const : <a b> a -> b -> a := \a _ -> a;
add : int -> int -> int := \x y -> x + y;

Record Types (Product Types)

Definition

t := type <a b> { fst : a, snd : b }

Construction

make : <a b> a -> b -> t a b := \a b -> { fst : a, snd : b };

Field Access (Dot Notation)

p.fst    -- Access 'fst' field of record p
p.snd    -- Access 'snd' field of record p

Example: Pair Module

t := type <a b> { fst : a, snd : b }

make : <a b> a -> b -> t a b := \a b -> { fst : a, snd : b };
swap : <a b> t a b -> t b a := \p -> { fst : p.snd, snd : p.fst };
mapFst : <a b c> (a -> c) -> t a b -> t c b := \f p -> { fst : f p.fst, snd : p.snd };

Algebraic Data Types (Sum Types)

Definition with Constructors

t := type {
  | ConstructorA : <a> TypeSignature
  | ConstructorB : <a> TypeSignature
}

Example: Maybe (Option Type)

t := type {
  | None : <a> t a
  | Some : <a> a -> t a
}

Example: Either Type

t := type {
  | Left : <a b> a -> t a b
  | Right : <a b> b -> t a b
}

Pattern Matching

Use match expression with braces and commas:

match value {
  Pattern1 -> result1,
  Pattern2 -> result2,
  _ -> defaultResult
}

Patterns

Pattern Matches
ConstructorName Nullary constructor
ConstructorName x Constructor with binding
ConstructorName x y Constructor with multiple bindings
_ Wildcard (matches anything)

Examples

-- Maybe elimination
maybe : <a b> b -> (a -> b) -> t a -> b := \def f m ->
  match m {
    None -> def,
    Some x -> f x
  };

-- Either elimination
either : <a b c> (a -> c) -> (b -> c) -> t a b -> c := \f g e ->
  match e {
    Left x -> f x,
    Right y -> g y
  };

Modules

Declaration

ModuleName := module;           -- Forward declaration
ModuleName := module { ... };   -- With body

Imports

use Super.*;     -- Import all from parent scope
use Core.*;      -- Import all from Core module
use ModuleName;  -- Import specific module

Accessing Module Members

Module.member
Module.Type.constructor

Example

Functor := module {
  t := type <f> {
    map : <a b> (a -> b) -> f a -> f b
  }

  void : <f a> t f -> f a -> f () := \f fa -> f.map (\_ -> ()) fa
}

Type Classes

BechML uses record types to represent type class dictionaries:

Functor

Functor.t := type <f> {
  map : <a b> (a -> b) -> f a -> f b
}

Applicative

Applicative.t := type <f> {
  pure : <a> a -> f a,
  apply : <a b> f (a -> b) -> f a -> f b
}

Monad

Monad.t := type <m> {
  bind : <a b> m a -> (a -> m b) -> m b
}

Monoid

Monoid.t := type <a> {
  empty : a,
  append : a -> a -> a
}

Creating Instances

functor : Functor.t [] := {
  map : #list_map
};

monad : Monad.t t := {
  bind : \a f -> f a.run
};

Built-in Primitives

Primitives are prefixed with #:

Integer Operations

eq : int -> int -> bool := #int_eq;
lt : int -> int -> bool := #int_lt;
gt : int -> int -> bool := #int_gt;
leq : int -> int -> bool := #int_leq;
geq : int -> int -> bool := #int_geq;
add : int -> int -> int := #int_add;
sub : int -> int -> int := #int_sub;
mul : int -> int -> int := #int_mul;
div : int -> int -> int := #int_div;
mod : int -> int -> int := #int_mod;

List Operations

cons : <a> a -> [a] -> [a] := #list_cons;
append : <a> [a] -> [a] -> [a] := #list_append;
length : <a> [a] -> int := #list_length;
map : <a b> (a -> b) -> [a] -> [b] := #list_map;

IO Operations

print : string -> io () := #print;

IO Type Class Instances

#io_map, #io_pure, #io_apply, #io_bind

Control Flow

Conditionals

if condition then trueExpr else falseExpr

Example

replicate : <f a> t f -> int -> f a -> f [a] := \ap n fa ->
  if Int.leq n 0
    then ap.pure []
    else ap.apply (ap.apply (ap.pure (\x xs -> List.cons x xs)) fa) (replicate ap (Int.sub n 1) fa);

Operators

Arithmetic (via Int module)

Int.add x y    -- x + y
Int.sub x y    -- x - y
Int.mul x y    -- x * y
Int.div x y    -- x / y
Int.mod x y    -- x % y

Comparison (via Int module)

Int.eq x y     -- x == y
Int.lt x y     -- x < y
Int.gt x y     -- x > y
Int.leq x y    -- x <= y
Int.geq x y    -- x >= y

Common Patterns

Identity Function

id : <a> a -> a := \a -> a;

Constant Function

const : <a b> a -> b -> a := \a _ -> a;

Function Composition

compose : <a b c> (b -> c) -> (a -> b) -> a -> c := \f g x -> f (g x);

Functor Instance for Custom Type

functor : <s> Functor.t (t s) := {
  map : \f i -> { runUnsafe : (f (runUnsafe i)) }
};

Monad Transformer Pattern

StateT := module {
  use Super.*;
  use Core.*;

  t := type <s m a> { run : s -> m (Pair.t a s) }

  functor : <s m> Functor.t m -> Functor.t (t s m) := \fm -> {
    map : \f sa -> { run : \s -> fm.map (Pair.mapFst f) (sa.run s) }
  };
}

File Structure

  • Files use .bml extension
  • One module per file (typically)
  • Module name matches file name

Package Configuration

Uses roux.json for package management (Roux package manager)


Quick Reference Table

Concept Syntax
Type annotation name : Type
Assignment :=
Lambda \x -> expr
Generic params <a b>
Function type a -> b
List type [a]
Record type { field : Type }
Record literal { field : value }
Field access record.field
Sum type constructor | Name : Type
Pattern match match v { P -> e }
Wildcard _
Module module { ... }
Import use Module.*;
Primitive #primitive_name
Conditional if c then t else f
Statement terminator ;

Standard Library (Core)

Module Purpose
Int Integer operations
List List operations
Maybe Optional values (None/Some)
Either Sum type (Left/Right)
Pair Tuples/product types
IO Input/output effects
Identity Identity functor/monad
Reader Reader monad
State State monad
Writer Writer monad
Functor Functor type class
Applicative Applicative type class
Monad Monad type class
Monoid Monoid type class
MonadTrans Monad transformer class

Resources


Limitations

No Input Operations

BechML currently does not support input operations. The language only provides output capability through the print primitive:

print : string -> io () := #print;

There are no built-in primitives for:

  • Reading from stdin (getLine, readLine)
  • User input prompts
  • File reading operations

The IO module in the Core library defines monadic operations (#io_pure, #io_bind, #io_map, #io_apply) for sequencing IO effects, but only print is available for actual IO operations.

This limitation means BechML programs cannot:

  • Accept interactive user input
  • Read from files
  • Implement REPL-style interactions

For interactive applications, BechML would require extensions to the runtime and new primitives to be added to the compiler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment