Types and inference¶
ll-lang uses Hindley-Milner (Algorithm W) with let-generalization. Types are inferred bottom-up from literals. Annotations are required only where inference would be ambiguous or where you want to lock down a signature.
Base types¶
| ll-lang | Codegen (F#) | Literal form |
|---|---|---|
Int |
int64 |
42 |
Float |
float |
3.14 |
Str |
string |
"hello" |
Bool |
bool |
true, false |
Char |
char |
'c', '\n', '\\' |
Unit |
unit |
(no literal) |
Integer literals always produce Int, decimal literals always produce Float.
String and character literals share the escape set \n \t \\ \" \'.
Inferred signatures¶
double(x Int) = x * 2
-- inferred: Int -> Int
square(x Int) = x * x
-- inferred: Int -> Int
The return type is optional when the body's type is determined by usage of the parameters.
Polymorphism¶
Type variables in ll-lang use single uppercase letters (A, B, E, ...).
They are rigid and universally quantified over the function:
id(x A) A = x
-- ∀A. A -> A
Constructors of a parametric type introduce their parameters automatically:
Maybe A = Some A | None
-- Some : ∀A. A -> Maybe[A]
-- None : ∀A. Maybe[A]
Let-generalization¶
f =
g = \x. x
g
Here g is generalized at the binding — calling it with an Int and then a
Str in the same scope would both type-check.
When you must annotate¶
Parameters of top-level functions¶
The parser requires every parameter to carry a type:
add(a Int)(b Int) = a + b -- OK, each param annotated
bad(a)(b) = a + b -- parse error
Ambiguous polymorphism¶
If a function is fully polymorphic and its return has no connection to its input, annotate the return:
pure(a A) Maybe[A] = Some a
Without the Maybe[A] return annotation, the compiler cannot decide which
functor-like type a should be wrapped in.
Tagged parameters¶
Tags are never inferred automatically on parameters. To require a tagged argument you must write the annotation:
getUser(id Str[UserId]) Maybe[Str] = Some "alice"
Trait-constrained generics¶
Use bracket syntax before the parameter list:
transform[F: Functor](xs F[A])(f A -> B) = map f xs
This reads as "for any F with a Functor impl, and any A, B".
When you can elide¶
- Return type of a function whose body is a simple expression
- Types of local bindings chained by layout
- Types of lambda parameters bound to a known context
- Types of literals and constructor applications
Error tie-ins¶
The inference pass emits:
E001 TypeMismatchwhen unification fails on rigid base types (e.g. passingStrwhereIntis expected)E004 UnitMismatchwhen two tagged numeric types have the same base but different units (Float[m]vsFloat[kg])E005 TagViolationwhen an untagged value is passed where a tag is required (StrvsStr[UserId])E008 InfiniteTypeif unification would produce a cycle likea = List[a]
See 07-error-codes for reproducible examples.