Intermediate

Functions in Dylan

Learn how to define and use functions in Dylan - define function, generic functions and methods, return values, keyword parameters, recursion, multiple values, closures, and higher-order functions

Functions are where Dylan’s multi-paradigm character becomes obvious. Dylan is object-oriented, functional, and procedural all at once, and its function model reflects that blend. You can write plain named functions like you would in C or Python, but you can also attach multiple methods to a single generic function and let the runtime pick the right one based on the types of all the arguments — a feature called multiple dispatch, inherited from Common Lisp’s CLOS.

Like its Lisp ancestors, Dylan treats functions as first-class values. A function can be stored in a variable, passed as an argument, returned from another function, and built on the fly as an anonymous method. Functions also have no return keyword: the value of the last expression in the body is what the function returns, and the optional => (...) clause documents (and can constrain) those return values.

In this tutorial you’ll define functions with define function, add methods to generic functions for multiple dispatch, return more than one value at once, use keyword parameters with defaults, write recursion, and finish with closures and higher-order functions. We’ll go well beyond the one-line greet from the Hello World page.

Defining Functions, Returning Values, and Recursion

The define function form creates a named function. Parameters are listed with optional :: type declarations, and the => (...) clause names the return values. Because the last expression is returned automatically, a one-line body needs no return.

Dylan also distinguishes a plain function from a generic function. When you write define method with the same name more than once, each method joins one generic function, and Dylan selects the matching method by the runtime types of the arguments. The example below shows both styles, plus multiple return values via values and a recursive factorial.

Create a file named functions.dylan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Module: functions

// `define function` makes a plain named function.
// `=> (result :: <integer>)` declares the type of the returned value.
define function square (n :: <integer>) => (result :: <integer>)
  n * n
end function;

// Two methods on one generic function `describe-number`. Dylan picks the
// method by the runtime type of the argument — this is multiple dispatch.
define method describe-number (n :: <integer>) => ()
  format-out("%d is an integer\n", n);
end method;

define method describe-number (n :: <float>) => ()
  format-out("%= is a float\n", n);
end method;

// Recursion: a function calls itself until it hits the base case.
define function factorial (n :: <integer>) => (result :: <integer>)
  if (n <= 1)
    1
  else
    n * factorial(n - 1)
  end if
end function;

// `values` returns more than one result at once.
define function min-max (a :: <integer>, b :: <integer>)
    => (smaller :: <integer>, larger :: <integer>)
  if (a < b)
    values(a, b)
  else
    values(b, a)
  end if
end function;

// `#key` declares optional keyword parameters with default values.
define function greet (name :: <string>, #key greeting = "Hello") => ()
  format-out("%s, %s!\n", greeting, name);
end function;

// --- Top-level expressions run when the program starts ---

format-out("square(7) = %d\n", square(7));

describe-number(42);
describe-number(3.5);

format-out("factorial(5) = %d\n", factorial(5));

// Receive multiple return values with `let (a, b) = ...`
let (lo, hi) = min-max(9, 4);
format-out("min = %d, max = %d\n", lo, hi);

greet("World");
greet("Dylan", greeting: "Welcome");
  • define function defines a plain function; define method adds a method to a generic function of the same name.
  • => (result :: <integer>) documents and constrains the return value. Use => () when a function returns nothing useful.
  • Multiple dispatch: calling describe-number runs the <integer> method or the <float> method depending on the argument’s actual type.
  • values(a, b) returns two results, and let (lo, hi) = ... destructures them.
  • #key greeting = "Hello" makes greeting optional; at the call site you pass it as greeting: "Welcome".

Closures and Higher-Order Functions

Functions in Dylan are values like any other, so you can pass them around and return them. An anonymous function is written method (args) ... end. A function that returns another function creates a closure — the inner method “remembers” the variables that were in scope when it was created. Functions that accept or return other functions are higher-order functions, and Dylan’s collection library leans on them heavily with map, reduce, and choose.

Create a file named closures.dylan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Module: closures

// A higher-order function: `f` is itself a function.
define function apply-twice (f :: <function>, x) => (result)
  f(f(x))
end function;

// A closure: `make-adder` returns a method that captures `n`.
define function make-adder (n :: <integer>) => (adder :: <function>)
  method (x :: <integer>) => (sum :: <integer>)
    x + n
  end method
end function;

let add5 = make-adder(5);

format-out("add5(10) = %d\n", add5(10));
format-out("apply-twice(add5, 10) = %d\n", apply-twice(add5, 10));

let nums = #(1, 2, 3, 4, 5);

// `map` applies a function to every element, returning a new sequence.
let squares = map(method (x) x * x end, nums);
format-out("squares = %=\n", squares);

// `reduce` folds a sequence into one value: (function, initial-value, sequence).
// `\+` refers to the `+` operator as a first-class function value.
let total = reduce(\+, 0, nums);
format-out("sum = %d\n", total);

// `choose` keeps only the elements matching a predicate (like filter).
let evens = choose(even?, nums);
format-out("evens = %=\n", evens);
  • method (x) ... end is an anonymous function value; it needs no name and can be passed directly to map.
  • make-adder returns a method that closes over n, so add5 always adds 5 — that captured environment is the closure.
  • map, reduce, and choose are higher-order functions from Dylan’s collection library that take a function as an argument.
  • \+ turns the + operator into an ordinary function value you can hand to reduce.

Running with Docker

The Docker image compiles and runs the Dylan source file in the mounted directory. Create and run one example at a time — keep a single .dylan source in the directory, then replace it to try the next example.

1
2
3
4
5
6
7
8
# Pull the Dylan image
docker pull codearchaeology/dylan:latest

# With functions.dylan in the current directory:
docker run --rm -v $(pwd):/app codearchaeology/dylan:latest

# Replace it with closures.dylan and run again:
docker run --rm -v $(pwd):/app codearchaeology/dylan:latest

Expected Output

Running functions.dylan:

square(7) = 49
42 is an integer
3.5 is a float
factorial(5) = 120
min = 4, max = 9
Hello, World!
Welcome, Dylan!

Running closures.dylan:

add5(10) = 15
apply-twice(add5, 10) = 20
squares = #(1, 4, 9, 16, 25)
sum = 15
evens = #(2, 4)

Key Concepts

  • define function creates a plain named function, while define method adds a method to a generic function that can have many implementations.
  • Multiple dispatch selects the method to run based on the runtime types of all arguments, not just a receiver object — a defining feature Dylan shares with CLOS.
  • No return keyword — a function evaluates to the value of its last expression; the => (...) clause names and constrains the results.
  • values returns multiple results at once, destructured at the call site with let (a, b) = ....
  • #key parameters are optional and can supply defaults, passed by name (greeting: "Welcome") when calling.
  • Recursion is natural and idiomatic; always provide a base case (here, n <= 1) to terminate.
  • Functions are first-class values — store them in variables, pass them as arguments, and return them, using method (args) ... end for anonymous functions.
  • Closures capture surrounding variables, and higher-order functions like map, reduce, and choose use functions as data to drive Dylan’s collection processing.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/dylan:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining