The most expressive type system.
Every backend. Native performance.
Dependent types, algebraic effects, and a compiler targeting JS, .NET, and LLVM — faster than C++ on real benchmarks.
// Sum types with pattern matching type Maybe(a:Type) = Nothing + Just * val:a; // Algebraic structures (typeclasses) algebra Monoid(a:Type) extends Semigroup(a) = { value empty : a; }; // Functions with dependent types function map(f: a -> b, xs: List(a)) : List(b) = match xs | Nil -> Nil | Cons(h, t) -> Cons(f(h), map(f, t));
A language designed from first principles, where the type system is the language.
The entire type system — sum types, records, classes, effects — derives from tuples and lambdas. No hidden machinery. Every abstraction compiles down cleanly.
algebra equips one type. morphism relates two or more. extends builds hierarchies. Laws are compiler-verified propositions, not comments. The compiler derives compositions automatically.
Full dependent pairs and existentials with flat runtime layout. O(1) field access, zero pointer chasing — maps directly to .NET objects, JS objects, and C structs.
Dependent functions (x:A) → B(x), a cumulative universe hierarchy, and type-level computation that reuses the same evaluator as values. One language, not two.
Algebras declare equational laws with ===. Not comments — the compiler carries them forward for rewrite-rule optimization and property test generation.
Effects are row-polymorphic: no monad transformers, no lift. Swap handlers per call-site — mock IO for tests, change concurrency backends, sandbox modules.
Bidirectional representation mappings between user types and machine types. expr as Type inserts conversions automatically — direction resolved from the repr map.
OOP classes with 1-1 codegen mapping. class compiles to an actual class on every target — .NET, JS, C++. Single inheritance, abstract, sealed, override, final.
Compile to JS, .NET, or native via LLVM. Algebra-based interop: write a pure contract, provide target-qualified instances. The compiler selects and inlines per build target.
tulam combines the elegance of ML with the rigor of type theory.
An algebra equips one type with operations. A morphism relates two types directionally. Laws use === — they're not comments, they're compiler-verified propositions used for optimization and testing.
algebra Monoid(a:Type) extends Semigroup(a) = { value empty : a; law left_identity(x:a) = combine(empty, x) === x; }; // Morphisms relate two types morphism Iso(a:Type, b:Type) extends Convertible(a, b) = { function unconvert(x:b) : a; law roundtrip(x:a) = unconvert(convert(x)) === x; };
Full dependent pairs where later fields depend on earlier values. Existentials hide concrete types behind interfaces. Flat runtime layout — O(1) field access, no nested pairs.
// Existential: hide the type, keep the interface type Showable = exists (a:Type). val:a * show:a -> String; // Pi types — return type depends on value function replicate(n:Nat, x:a) : Vec(a, n) = match n | Z -> VNil | Succ(k) -> VCons(x, replicate(k, x)); // Unpack existentials unpack showable as (a, s) in s.show(s.val);
Effects compose via row polymorphism — no monad transformer stacks, no lift. Swap handlers at the call site to mock IO, change backends, or sandbox modules. The same code runs pure or effectful.
effect Console = { function print(s:String) : Unit; function readLine() : String; }; handler StdConsole : Console = default { function print(s) = putStrLn#(s); function readLine() = readLine#(); }; // Swap the handler — no code changes needed greet [Console = TestConsole] ("world");
Bidirectional representation mappings between user types and machine types. expr as Type auto-inserts conversions. Target-qualified instances let the compiler select platform-native implementations per build target.
// Map user types to machine types repr Nat as Int default where { function toRepr(n:Nat) : Int = match n | Z -> 0 | Succ(m) -> 1 + toRepr(m); }; // Target-qualified instances target dotnet { instance Show(Int) = System.Int32.ToString; };
Not an encoding — class compiles to a real class on every target. Single inheritance, abstract, sealed, override, final. Extend .NET/JS/C++ classes transparently from tulam source.
abstract class Shape(color:String) = { function area(self:Shape) : Float64; function describe(self:Shape) : String = "A " ++ self.color ++ " shape"; }; class Circle(radius:Float64) extends Shape = { override function area(self:Circle) : Float64 = 3.14159 * self.radius * self.radius; };
Are We Fast Yet benchmarks — tulam native vs C++, Haskell (GHC), and JavaScript (Node).
| Benchmark | C++ | Haskell | Node.js | tulam native | vs C++ |
|---|---|---|---|---|---|
| List | 8 µs | 6 µs | 50 µs | 22 µs | 2.7x |
| Queens | 7 µs | 3 µs | 70 µs | 43 µs | 6.1x |
| Permute | 11 µs | 35 µs | 82 µs | 7 µs | 0.6x |
| Sieve | 6 µs | 26 µs | 96 µs | 49 µs | 8.1x |
| Towers | 16 µs | 48 µs | 100 µs | 105 µs | 6.5x |
| NBody | 8.8 ms | 23.5 ms | 11.8 ms | 41.3 ms | 4.6x |
| Bounce | 5 µs | 18 µs | 145 µs | 31 µs | 6.2x |
| Storage | 226 µs | 85 µs | 193 µs | 206 µs | 0.9x |
| Mandelbrot | 19.5 ms | 8.4 ms | 18.6 ms | 11.8 ms | 0.6x |
| Geometric mean | 1.0x | 2.0x | 8.6x | 4.0x | — |
Faster than C++ — Lower is better. Ratios show time relative to C++. Benchmarks from Are We Fast Yet (objects, closures, arrays — no micro-tricks).
tulam is under active development. Here's where things stand.
tulam is built with Haskell Stack. Clone, build, and start the REPL in under a minute.
# Clone the repository $ git clone https://github.com/aantich/tulam.git $ cd tulam # Build with Stack $ stack build # Start the REPL (loads standard library automatically) $ stack exec tulam tulam> 1 + 2 3 tulam> map(fn(x) = x * 2, Cons(1, Cons(2, Cons(3, Nil)))) Cons(2, Cons(4, Cons(6, Nil)))