TypeFighter

TypeFighter is a small, experimental language built around a modern, inference-first type system. The headline feature is structural records instead of nominal ones: records are compared by the fields they actually have, not by a declared name — so a function that needs { name: String } accepts any record with that field, no boilerplate declarations required. On top of that: row polymorphism, set-theoretic literal and union types, and classical polymorphic functions — all figured out by the type checker with (almost) no annotations. This site is generated from the test suite: each test is a minimal example of what the type system can — and can't yet — do.

What's in, what's not

A quick comparison against concepts you'll recognize from Rust, Haskell, Scala, TypeScript, Swift, or F#:

In today

  • Structural records — matched by fields, not by name
  • Row polymorphism — "has at least field X"
  • Literal & union types — booleans are modelled as the union true | false; each literal is a type on its own
  • Intersection types (partial)
  • Polymorphic functions — fresh instantiation at every use
  • Higher-order & partial application
  • Arrays as a built-in type constructor
  • Inference with (almost) no annotations

Not yet (or absent by design)

  • Let-generalization — inner let bindings don't auto-generalize the way classical Hindley–Milner does
  • Traits / type classes / protocols — no ad-hoc polymorphism (see below)
  • Sum types / ADTs — no Option/Result-style constructors; literal unions cover a subset
  • Pattern matching — no match … with construct
  • Recursion — no let rec or fix-point combinator
  • Implicit conversions / subtypingNumber is never silently a String
  • Nominal types — everything is structural (by design, not a gap)
  • Surface syntax — programs are built via the F# DSL, there is no parser from text
  • Effect system — effects aren't tracked

A note on Traits

In languages like Rust (traits), Haskell (type classes), Swift (protocols), or Scala (given/implicits), you can write a function like show : a -> String that works on any type a which implements a Show constraint. TypeFighter has no such mechanism — there is no way to say "any type that supports toString" or "any Num". The closest thing TypeFighter offers is row polymorphism: a function fun r -> r.name works on any record that structurally has a name field. That gives you "works on anything with field X" — but not "works on anything supporting operation X".

How to read these pages

Every test is shown with three things:

  • A short doc block giving the environment, source expression, and inferred type (or expected error).
  • An optional note with extra context about what the test demonstrates.
  • The raw F# test body — toggle it open to see the AST construction.

Tests marked Not yet implemented document features that are deliberately not supported today.

Categories (49 tests total)

Literals 4 tests

Numbers, strings, and booleans are the primitive literal forms. Booleans are modeled set-theoretically as the union of the two literal values true and false — so every boole…

Functions 8 tests

Lambda abstractions, application, partial application, and the shape of higher-order types. A lambda without usage constraints generalizes to a polymorphic type at its enclosing…

Let bindings 5 tests

let introduces a name with two key properties: 1. Scope — the name is in scope only within the body. 2. Generalization — if the bound value has free type variables, those are…

Generalization 4 tests

Turning a MonoTyp with free type variables into a PolyTyp binds those variables as forall-quantified. Alpha-equivalent polytypes (same shape, different var numbers) are normaliz…

Arrays 4 tests

Arrays are homogeneous — all elements must unify to a single element type. Mixing incompatible element types is a static error.

Records 8 tests

This file covers record creation and flat property access on fully-known records. For row-polymorphic access through lambda parameters, see Rows.fs.

Rows (row-polymorphic record access) 10 tests

When a function accesses fields of a record it receives as an argument, the inferencer collects the required fields and reconstructs a record type that contains exactly those…

Polymorphism (via env-supplied polytypes) 2 tests

Polytypes provided through the environment are instantiated fresh at every use site — the same name can be used at different types. For AST-level let generalization, see Let.fs.

Intersection types 3 tests

An intersection R1 & R2 behaves as a value that conforms to both record shapes. Field access is only allowed when both sides agree on the field name and its type.

Type hierarchies / implicit conversions 1 tests

Scenarios where one type could be coerced to another — for example where a String is expected but a Number is given. Currently unsupported: unification rejects the mismatch…