Skip to content

Paradigm system

Each source file declares which paradigms it uses with @paradigm directives. Those directives unlock the builtins, syntax, and keywords tied to each paradigm. They do not affect which functions from other files can be called. Only builtins and syntax are gated, and only within the current file.

This page is normative. For a walkthrough with worked examples, see the paradigms guide.

@paradigm <name> appears at the top of the file, before declarations, alongside @import directives. It can be repeated to stack paradigms.

@paradigm functional
@paradigm procedural
@import std.io

Imports are independent of paradigm directives. Importing a module does not grant any paradigm; the two systems do not interact. See Source files for directive placement and import syntax.

DirectiveUnlocks
@paradigm functionalmap, filter, reduce, fold, foreach, do notation, monad keyword, pure function enforcement
@paradigm proceduralfor, while, do while, mut variables
@paradigm oopinterface, composition syntax

Some features that might look paradigm-gated are not:

  • Structs are plain data containers available across all paradigms, not gated by @paradigm oop. Methods can be associated with structs through impl.
  • Lambdas are not gated. The functional builtins take lambdas, but lambdas themselves are available everywhere.
  • spawn, join, and submit are always-available builtins, gated behind no paradigm, like alloc and read_file.

The full list of always-available builtins is in Builtins. The gated constructs are covered in detail in Functional concepts and Object-oriented concepts.

Directives stack. The set of available builtins and syntax is the union of all declared paradigms. This program uses a functional builtin and procedural mutation in the same file:

stacked.dusk
@paradigm functional
@paradigm procedural
func main() -> int32 {
nums: int64[] = [1, 2, 3, 4, 5]
doubled := map(nums, lambda (n: int64) -> int64 { return n * 2 })
mut sum: int64 = 0
for x in doubled {
sum = sum + x
}
println(sum)
return 0
}

If no @paradigm directive is present, the file defaults to procedural. This file compiles without any directive:

default_procedural.dusk
func main() -> int32 {
mut i: int64 = 0
while i < 3 {
println(i)
i = i + 1
}
return 0
}
  • Functions and types defined in any file are paradigm agnostic. They can be called or used from any other file regardless of paradigm directives.
  • Gating is per file and covers builtins and syntax. A file without @paradigm functional cannot call map directly, but it can call a user-defined function that internally uses map.
  • The check is intra-file and runs during semantic analysis. There is no link-time paradigm check, since calls through user-defined functions are never gated.
  • There is no paradigm restriction on exports. An exported function or type is usable from any file regardless of either file’s paradigm directives.

Using an undeclared paradigm feature is a compile error in that file. The diagnostic names the missing directive:

gated.dusk: 3:16: error: the 'map' builtin requires the functional paradigm; add '@paradigm functional'

The same shape of error is reported for procedural constructs (for, while, do while, mut) and OOP constructs (interface, composition) when their paradigm is not declared, and for the monad keyword and do notation, which belong to the functional paradigm.