FunctionPredicate: Design Notes
First-class function types in TJS, using the same pattern as Type/Generic.
The Problem
TJS has no way to express "this parameter must be a function with this signature." Currently:
() => voidin TypeScript becomesundefinedin fromTS output- There's no TJS syntax for function-typed parameters
- Callbacks, event handlers, and higher-order functions lose their type information at the boundary
Design Principles
- Functions are values — a function should be usable as a type example,
just like
0means "integer" and''means "string" - FunctionPredicate should work like Type/Generic — same pattern of predicate-based checking, introspection via metadata
- The return contract is part of the type —
:,:?, and:!are meaningful distinctions in the function's contract
The Three Return Contracts
| Marker | Name | Meaning |
|---|---|---|
: |
returns |
Verified at transpile time (signature test) |
:? |
checkedReturns |
Verified at transpile time AND runtime |
:! |
assertReturns |
Declared but not verified (metadata only) |
These are not just build options — they describe the trust level of
the function's return type. A function with :? makes a stronger promise
than one with :!.
Syntax: Function as Type Example
The most TJS-idiomatic approach — a function IS its own type:
// This function's signature IS a type
function formatter(input: '', options: { locale: 'en' }):? '' {
return input
}
// fn must match formatter's contract
function process(fn: formatter) {
const result = fn('hello', { locale: 'fr' })
}
The runtime check for fn: formatter:
typeof fn === 'function'fn.__tjsexists (it's a TJS-typed function)fn.__tjs.paramsshape-matchesformatter.__tjs.paramsfn.__tjs.returnsmatchesformatter.__tjs.returns
Untyped functions (no __tjs) would fail the check — they don't have
the metadata to verify against. Use ! (unsafe) to skip the check for
interop with plain JS callbacks.
Syntax: Explicit FunctionPredicate
For cases where you want to declare a function type without writing an example function:
FunctionPredicate Formatter {
description: 'formats a string with locale options'
params: { input: '', options: { locale: 'en' } }
returns: ''
}
// Or with checked returns:
FunctionPredicate Validator {
params: { value: null }
checkedReturns: false
}
// Or declared-only returns:
FunctionPredicate Callback {
params: { event: { type: '', target: null } }
assertReturns: undefined
}
Syntax: FunctionPredicate from Function
Create a type from an existing function's metadata:
function myFormatter(input: '', options: { locale: 'en' }):? '' {
return input
}
// Extract the type from the function
FunctionPredicate Formatter(myFormatter, 'string formatter with locale')
This is analogous to Type Name 'example' — the function itself is the
example value, and its __tjs metadata defines the type.
Runtime Representation
A FunctionPredicate at runtime would be an object with:
{
check(fn) { ... }, // returns boolean
params: { ... }, // param descriptors
returns: { ... }, // return type descriptor
returnContract: 'checked' | 'returns' | 'assert',
description: '...',
default: exampleFn, // the example function, if provided
}
This matches the shape of Type() — check, default, description.
Validation Levels
When checking fn: SomeType where SomeType is a FunctionPredicate:
| Check | What it verifies |
|---|---|
typeof fn === 'function' |
It's callable |
fn.__tjs exists |
It's a TJS-typed function |
| Param count matches | Same arity (or compatible) |
| Param types match | Each param's type descriptor matches |
| Return type matches | Return type descriptor matches |
| Return contract | At least as strict as required |
Return contract strictness: checkedReturns (-?) > returns (->) > assertReturns (-!).
A checkedReturns function satisfies any requirement.
A returns function satisfies returns or assertReturns.
An assertReturns function only satisfies assertReturns.
Compatibility with Untyped Functions
Plain JS functions have no __tjs metadata. Options:
- Strict: Reject untyped functions (safe but hostile to JS interop)
- Lenient: Accept any function, only validate if
__tjsexists - Unsafe marker: Use
!to skip the check for known-untyped callbacks
Option 3 is most consistent with TJS's existing patterns:
// Strict — fn must have matching __tjs metadata
function process(fn: formatter) { ... }
// Lenient — fn just needs to be callable
function process(! fn: formatter) { ... }
Relationship to Existing Features
- Type: FunctionPredicate IS a Type — just one that checks function
signatures specifically. Could be implemented as a special case of Type
with a built-in predicate that introspects
__tjs. - Generic: FunctionPredicate could be generic too —
FunctionPredicate Mapper<T, U> { params: { value: T }, returns: U } - declaration block: FunctionPredicates would benefit from declaration
blocks for
.d.tsemission, same as Generic.
Implementation Path
- Runtime: Add
FunctionPredicate()to the TJS runtime alongsideType()andGeneric(). Returns a type guard that checks__tjsmetadata on functions. - Parser: Recognize
FunctionPredicateas a declaration keyword (same asType,Generic). Parse the block or function-argument form. - Metadata: The
__tjsmetadata for the return type already includestype— add acontractfield for the marker. - fromTS: When converting
(x: number) => stringtypes, emit a FunctionPredicate instead ofundefined. - Inference: When a function is used as a
:param type, check if it has__tjsmetadata and validate the caller's function against it.