rohlang3: a point-free, homoiconic, and dependently typed SK calculus
A small, experimental SK-like language with a dependent-type flair—and a pinch of reflection, partial evaluation, and environment reordering—implemented in Rust. What's cool about it is that typing context is a first-class construct that can be manipulated by the program itself.
Also even though I (and o1-pro) wrote it, I can't really even think of a good example to write. Also, I am 100% sure there are many, many, logical inconsistencies and paradoxes (for one, Girard's paradox). If you want a more complete look at the (obvious) issues check issues.md.
- Introduction
- Overview
- Key Concepts
- Language Features
- Installation & Usage
- REPL Usage
- Example Snippets
- Project Structure
- Contributing
rohlang3 is a self-contained functional programming language and experimental type system built in Rust. It’s inspired by SK combinator calculus, extended with a small dependent type system, reflection constructs, and novel context reordering combinators. The language is an exploration of minimalist combinator-based programming with new possibilities for typed, partially-evaluated, and reflective expressions.
It’s still a small prototype, suitable for language enthusiasts, researchers in typed functional calculi, or anyone curious about how to build a language from scratch in Rust. Our main goals are to:
- Explore how SK-like combinators can be enriched with a type system.
- Provide reflection capabilities—allowing the language to “see” and “quote” its own expressions.
- Support partial evaluation, ensuring that known combinator patterns can be simplified at compile- or run-time.
- Manage a lightweight dependent type system, giving new ways to specify and check program correctness.
See Plan.md for future steps.
In rohlang3, programs are made of combinator applications and symbolic atoms. Rather than conventional lambda abstractions, we use fundamental building blocks (s, k, i, etc.) that can be combined to encode higher-level constructs. On top of that, we allow expressions like (p A B), which acts like a dependent function type (a Pi type), and (g A B), which acts like a dependent pair (a Sigma type).
Additionally, rohlang3 introduces:
- A single universe
u, so that types themselves can reside inu(depending on usage). - Reflection combinators
qandefor quoting and evaluating expressions. - Partial evaluation combinator
zfor on-the-fly simplifications. - Context reordering combinators
i,E, andDfor referencing, exchanging, and duplicating the top of a "type context" stack.
The language supports the following core constructs:
- Atoms (e.g.,
s,k,u, or user-defined symbols). - Applications: Written as
(f arg1 arg2 ...). Internally represented byExpr::App(Box<Expr>, Vec<Expr>).
sandk: The standard SK combinators.i: An identity-like combinator referencing the top of the context.E: Exchanges the top two elements in the context.D: Duplicates the top element in the context.qande: Reflection: quote and evaluate.z: Partial evaluation.r,f,d: Construct and destruct pairs.
u: The universe or type-of-types.p: Pi type constructor,(p domain body).- E.g.,
(p u (k u))is “a function that, for any type A inu, returnsuagain.”
- E.g.,
g: Sigma type constructor,(g domain body).- E.g.,
(g u (i))means “a dependent pair overu, with the second component also referencing the binder.”
- E.g.,
q: Quote an expression, e.g.(q foo). This produces a quoted AST at runtime.e: Evaluate a quoted AST if it’s well-typed:(e (q foo))=>fooiffootype-checks.z: Trigger partial evaluation. If the expression matches known rewrite patterns (like(s (k k))=>i),zattempts to simplify it eagerly.
- Single universe
u: all well-formed types reside inu. - Dependent function types (
p) and dependent pair types (g). - Context-based type inference: references (
i, etc.) rely on an internal stack in the type checker. - Built-in checks for well-formedness: e.g.,
(p u (i))has typeu. - Reflection type-check:
(e (q expr))only proceeds ifexpris well-typed.
- Normal-order rewriting of combinators.
- Combinators like
s,k,f, anddhave fixed rewrite rules:f (r x y) => xd (r x y) => ys f g x => (f x) (g x), etc.
icombinator references the top of the context (i.e., the last type pushed).Eswaps the top two context elements.Dduplicates the top context element.
r: Pair constructor(r x y).f(first) andd(second) are pair destructors.- Dependent pairs via
(g domain body)are recognized at type-check time as well.
q: “quote,” stores an expression as data (an AST).e: “evaluate,” type-checks the quoted AST, and if valid, splices it into runtime evaluation.- Any unknown symbols inside
(q ...)can result in a type error at reflection time.
(z X)tries to partially evaluateXby applying certain known combinator optimizations. For instance:(s (k k))recognized as an identity combinator, so(s (k k) X)=>X.
- Repeated partial evaluation steps until a fixpoint is reached.
-
Clone the repository or download the source:
git clone https://github.com/username/rohlang3.git
-
Build with Cargo:
cd rohlang3 cargo build --release -
Run the REPL directly:
cargo run
or
cargo run --release
-
Interpret a file containing rohlang3 expressions:
cargo run --release my_program.rhl
Where
my_program.rhlhas one expression per line, ignoring blank lines and lines starting with#.
When you start rohlang3 with no arguments, you enter an interactive prompt:
$ cargo run
Starting rohlang3 REPL...
Welcome to rohlang3 REPL with environment + shared context.
Type `store <NAME> <expr>` to define a named expression.
Type `type <NAME>` to see the type of a stored expression.
Type `quit` or `exit` to leave.
>
Commands
store <NAME> <expr>: Defines<NAME>in the environment, storing<expr>(parsed & type-checked) for future reference.type <NAME>: Prints the type of the stored expression<NAME>.- Any other line is parsed as an expression to be immediately evaluated.
Example:
> store myId (p u (i))
Stored `myId`.
> type myId
Type of `myId` is:
u
> (myId u)
(p u (i))
> quit
Bye!
-
Simple SK
(s (k k) something) ;; The "i combinator" usageThis partly evaluates to
something. -
Dependent Pi
(p u (k u))
A function from any type
A : utou. The type of this expression is alsou. -
Reflection
(e (q (p u (k u))))
Checks if
(p u (k u))is well-typed, then evaluates it. Since it’s well-typed, it stays the same under normal-order evaluation. -
Pairs
(f (r s k)) ;; => s (d (r s k)) ;; => k
-
Partial Evaluation
(z (s (k k) u)) ;; => uBecause
(s (k k))is recognized as the identity combinator.
.
├── Cargo.toml
├── Cargo.lock
├── README.md
├── Plan.md
├── slides.latex
├── src
│ ├── ast.rs # Core AST definitions
│ ├── builtins.rs # Metadata/type info for built-in combinators
│ ├── evaluator.rs # Normal-order & partial evaluation
│ ├── parser.rs # Simple s-expression parser
│ ├── type_checker.rs # Dependent-type checking logic
│ ├── repl.rs # REPL implementation
│ ├── lib.rs # Library entry point
│ └── main.rs # Main CLI entry
└── tests
└── integration_tests.rs
ast.rs: Definition of theExprenum and helper constructors.builtins.rs: Provides recognized built-ins and their "placeholder" types.evaluator.rs: Implements normal-order rewriting and partial evaluation.parser.rs: Tokenizes and parses input text into our AST.type_checker.rs: Contains the core type inference & context-based checks.repl.rs: Simple interactive REPL with commands to store and retrieve expressions in an environment.tests: Integration tests verifying type checking, evaluation steps, reflection, and partial evaluation.
Plz contribute. Email me if you're curious about anything: rohanganapa@gmail.com.