Extern bindings
An extern declaration is a typed boundary between Noodle and the host
runtime. The Noodle source declares the name, parameters, and result type; a
companion ES module supplies the implementation. The compiler type-checks
calls against the Noodle declaration, but it does not inspect the JavaScript
function’s body.
Declare an external function
Section titled “Declare an external function”An external function has no Noodle body. Use @link when the host export has a
different name from the Noodle declaration:
module Clock
@link("now_ms")export extern func now_milliseconds() -> Int
func main() -> Unit do Debug.trace(now_milliseconds())endPut the host implementation in a companion file next to the primary source:
clock.nlclock.extern.mjsThe companion’s named export must match the @link key:
export const now_ms = () => Date.now();For a source file named clock.nl, the JavaScript backend looks for
clock.extern.mjs. A missing companion is a build error. A missing or
non-callable named export, an exception thrown by the companion, or a value
that violates the declared boundary follows the host runtime’s normal failure
behavior; Noodle does not insert an implicit conversion layer.
The binding name
Section titled “The binding name”With @link("key"), key is the exact named export selected from the
companion module. @link accepts no argument or one string argument:
module Environment
export extern func read_mode() -> String
@link("set_mode")export extern func write_mode(value : String) -> UnitThe first declaration binds to read_mode because no @link attribute was
provided. The second binds to set_mode. The binding key is implementation
metadata; it is not part of the exported function type or the module’s public
signature. Consumers only see the typed Noodle declaration.
extern is a contextual word. It introduces an external declaration only in
the expected declaration forms; it remains available as an ordinary lower-case
identifier elsewhere. External functions may be generic, but their parameters
are positional. They use the same visibility rules as ordinary functions:
without export, the declaration is private to its module.
External methods and datatypes
Section titled “External methods and datatypes”An extension can provide an external method when its implementation needs a host operation but should be called with dot syntax:
module NativeText
extension TextMethods for String @link("text_length") extern method length(value : String) -> IntendThe host companion exports text_length with the same positional calling
shape:
export const text_length = (value) => value.length;An extern datatype is different from an external function. It declares an
opaque nominal type whose representation is supplied by the host and whose
constructor set is empty:
module JsonBridge
export extern datatype JsonHandle end
@link("parse_json")export extern func parse(text : String) -> JsonHandleNoodle cannot construct, pattern-match, or structurally decompose
JsonHandle. Values enter or leave that type only through external functions
whose declarations describe the operations the host supports. Define explicit
debugging, equality, or conversion operations when the host-backed type needs
them; structural behavior is not generated automatically.
Package and artifact boundaries
Section titled “Package and artifact boundaries”The companion belongs to the package containing the primary .nl source. It
is not a separately importable Noodle module and it is not listed in
nlpkg.json as a source dependency. An exported extern function is published
to other Noodle modules through its ordinary function signature; the absolute
path of the .extern.mjs file is kept out of .nli signatures.
Parts do not create extra host boundaries. If a primary source includes
functions.part, an extern declaration in that part still uses the primary
module’s one .extern.mjs companion. A .part file never gets its own
companion.
Keep host code narrow: use an extern binding for platform capabilities that Noodle cannot perform directly, such as filesystem access, process control, JavaScript APIs, or a promise bridge. Put ordinary language logic in Noodle so it remains statically checked, testable, and portable across host setups.
For an asynchronous host operation, return the standard Task shape and bridge
the host promise according to the asynchronous code guide.
For compiler-provided operations that require compiler knowledge, use the
standard library’s native or runtime-provider mechanisms instead of inventing
an extern binding; see Structural capabilities
for an example of the distinction.
Next: Advanced pattern matching.