UFCS: Unified Function Call Syntax
UFCS: Unified Function Call Syntax
Section titled “UFCS: Unified Function Call Syntax”Status: Implemented (v0.2.1-2)
The Problem
Section titled “The Problem”Traditional OOP forces you to define methods inside classes:
class Player { void move(float dx, float dy) { ... }}This creates several issues:
- Closed for Extension: You can’t add methods to types you don’t own.
- Namespace Pollution: All methods live in the type’s namespace.
- Hidden Complexity: Method dispatch can involve v-tables and dynamic dispatch.
The Janus Solution: UFCS
Section titled “The Janus Solution: UFCS”In Janus, everything is a function. There are no “methods” in the traditional sense.
However, you can call functions using method syntax:
func move(p: *Player, dx: f64, dy: f64) do p.x = p.x + dx p.y = p.y + dyend
// Both work identically:move(player, 10.0, 5.0) // Proceduralplayer.move(10.0, 5.0) // Method-style (UFCS)How It Works
Section titled “How It Works”When the compiler sees obj.method(args):
- First: Check if
objhas a field namedmethod - Fallback: Look for a function
method(self: TypeOf(obj), args...) - Rewrite: Transform
obj.method(args)intomethod(obj, args)
This is pure syntactic sugar. No runtime overhead. No hidden magic.
Benefits
Section titled “Benefits”1. Extension Methods
Section titled “1. Extension Methods”Add “methods” to any type, even standard library types:
// Extend the built-in Vector typefunc sum(v: Vector) -> f64 do let total = 0.0 for i in 0..v.len() do total = total + v.get(i) end return totalend
// Now you can call it like a method:let numbers = vector_create()numbers.push(1.0)numbers.push(2.0)println(numbers.sum()) // 3.02. IDE Autocomplete
Section titled “2. IDE Autocomplete”Type player. and your IDE shows all functions where player is the first argument.
This is the killer feature for discoverability.
3. Method Chaining
Section titled “3. Method Chaining”player .move(10.0, 5.0) .damage(20) .heal(50)Reads naturally, compiles to simple function calls.
4. Honest Abstraction
Section titled “4. Honest Abstraction”Unlike OOP, there’s no hidden this pointer or implicit state. The first argument is explicit.
- First Argument Matters: For
obj.method(...)to work, there must be a functionmethod(self: TypeOf(obj), ...). - No Overloading (Yet): If multiple functions match, the first one found wins.
- Explicit is Better: You can always use procedural syntax if UFCS feels unclear.
LSP Support
Section titled “LSP Support”The Janus LSP server fully supports UFCS:
- Hover: Shows function signature when hovering over
player.move - Go to Definition (F12): Jumps to the
movefunction definition - Find References: Finds all uses of
move, whether called procedurally or as a method
Inspiration
Section titled “Inspiration”UFCS is borrowed from:
- Nim: Pioneered this approach
- D: Popularized it in systems programming
- Rust: Uses traits for similar ergonomics (but with more ceremony)
Janus takes the simplest, most pragmatic approach: just rewrite the call.
Example
Section titled “Example”See examples/ufcs_demo.jan for a complete demonstration.
Verdict: Pragmatic Evolution. We give you the ergonomics of OOP without the complexity of OOP.