Skip to main content

Functions and Actions

Dryft distinguishes between pure functions (fun) and effectful actions (act). This separation decouples logic from state, making code easier to understand, test, and optimize.

Why fun and act?

The distinction between pure and impure code is what matters, not just purity itself:
  • Pure functions (fun): Logic code that can be easily understood, tested, and optimized with memoization and compile-time evaluation
  • Impure actions (act): State and I/O code that is easily recognizable and portable
This separation helps you write better software by making side effects explicit.

Functions (fun)

Functions are pure - they:
  • Have no side effects
  • Always produce the same output for the same input
  • Cannot call actions
  • Can only manipulate the stack and compute values

Defining Functions

fun: function_name
    # function body
:fun
Or with semicolon terminator:
fun: function_name body ;

Simple Function Examples

# Increment a number
fun: inc 1 + ;

# Double a number
fun: double
    copy + ;

# Quadruple using composition
fun: quadra 
    double double ;

Functions with Type Signatures

(Int Int -> Bool)
fun: divby
    mod 0 =? ;

(Int -> Int)
fun: factorial var: n
    1 var: result
    cycle: $n 0 =? then break ;
        $result $n * result!
        $n 1 - n!
    :cycle
    $result
:fun

Function Composition

Functions naturally compose in concatenative style:
fun: inc 1 + ;
fun: double copy + ;
fun: square copy * ;

# Compose: (x + 1) * 2, then square it
5 inc double square  # Result: 144

Actions (act)

Actions can have side effects:
  • Perform I/O operations
  • Call other actions
  • Call pure functions
  • Modify external state

Defining Actions

act: action_name
    # action body
:act
Or with semicolon:
act: action_name body ;

Simple Action Examples

act: nl "\n" prints ;
act: space " " prints ;

act: hello
    "Hello, World!\n" prints ;

The main Action

Every Dryft program starts with the main action:
act: main
    "Hello, World!\n" prints ;

Actions with Parameters

Actions can accept parameters via the stack:
(Int ->)
act: fizzbuzz var: max
    1 var: x
    cycle: $x $max >? then: break ;
        when: 
            $x 15 divby then: "FizzBuzz!" prints ;
            $x  3 divby then: "Fizz" prints ;
            $x  5 divby then: "Buzz" prints ;
            $x printi ;
        "\n" prints
        $x 1 + x! 
    :cycle
:act

act: main 100 fizzbuzz ;

Purity Enforcement

Functions cannot call actions:
fun: bad_function
    "Hello" prints  # ERROR: Can't call action from function
:fun
This restriction is enforced at compile time. If an action is called inside a function scope, the compiler throws:
[DRYFT ERROR] Can not call actions from inside a function
Actions can call functions:
fun: double copy + ;

act: main
    5 double printi  # OK: action calls function
:act

Local Variables

Both functions and actions can use local variables:
fun: expo 
    var: x var: y
    1 var: total
    cycle: $x 0 =? then: break ;
        $total $y * total!
        $x 1 - x! 
    :cycle
    $total
:fun
See Control Flow for details on variables.

Calling Functions and Actions

Simply use the function or action name:
fun: inc 1 + ;
act: nl "\n" prints ;

act: main
    5 inc      # Call function
    printi     # Call action
    nl         # Call action
:act

Method Structure

Internally, both functions and actions are stored as methods with:
  • name: The function/action identifier
  • code: The compiled body
  • class: Either Function or Action
  • itypes: Input types consumed from stack
  • etypes: Output types produced to stack

Real-World Example

include: std/io

# Pure logic: check divisibility
(Int Int -> Bool) 
fun: divby
    mod 0 =? ;

# Pure logic: compute factorial
(Int -> Int)
fun: factorial var: n
    1 var: result
    cycle: $n 0 =? then break ;
        $result $n * result!
        $n 1 - n!
    :cycle
    $result
:fun

# Effectful action: safe division with I/O
(Int Int ->)
act: safediv 
    var: x var: y
    when:
        $x 0 equals? then: "Division by zero" prints ; 
        $y 0 equals? then: 0 printi ;
        $x 1 equals? then: $y printi ;
        $y $x / printi ;
:act

# Main program
act: main
    10 factorial printi "\n" prints
    6 1 safediv "\n" prints
:act

Benefits of Separation

Testability

Pure functions are trivial to test - just check input/output

Optimization

Functions can be memoized and evaluated at compile time

Reasoning

Know at a glance which code has side effects

Portability

Pure logic is easily portable across contexts

Summary

  • Use fun for pure, side-effect-free logic
  • Use act for I/O, state changes, and side effects
  • Functions cannot call actions (enforced at compile time)
  • Actions can call both functions and actions
  • This separation leads to cleaner, more maintainable code

Build docs developers (and LLMs) love