Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

User-defined Functions

ccalc supports user-defined named functions, multiple return values, and anonymous functions (lambdas) using Octave/MATLAB syntax.

Named functions

function result = name(p1, p2)
  ...
  result = expr;
end

Define a function at the top level in the REPL or in a .calc / .m script file. The function is stored in the workspace and can be called like any built-in. In script files, functions may appear anywhere — before or after the code that calls them (see Function hoisting in scripts).

Single return value

function y = square(x)
  y = x ^ 2;
end

square(5)     % 25

Multiple return values

function [mn, mx, avg] = stats(v)
  mn  = min(v);
  mx  = max(v);
  avg = mean(v);
end

[lo, hi, mu] = stats([4 7 2 9 1 5 8 3 6]);
% lo = 1   hi = 9   mu = 5

Discarding outputs

Use ~ in the assignment target to ignore individual outputs:

[~, top, ~] = stats([10 30 20]);   % top = 30

nargin — optional parameters

nargin holds the number of arguments actually passed:

function y = power_fn(base, exp)
  if nargin < 2
    exp = 2;   % default exponent
  end
  y = base ^ exp;
end

power_fn(5)     % 25   (exp = 2 by default)
power_fn(2, 8)  % 256

return — early exit

function result = factorial_r(n)
  if n <= 1
    result = 1;
    return      % exit immediately — no further code runs
  end
  result = n * factorial_r(n - 1);
end

factorial_r(7)   % 5040

Scope

Each call gets its own isolated scope:

  • The caller’s data variables are not visible inside the function.
  • Parameters are bound locally.
  • Other functions and lambdas from the caller’s workspace are forwarded, enabling self-recursion and mutual recursion.
function g = gcd_fn(a, b)
  while b ~= 0
    r = mod(a, b);
    a = b;
    b = r;
  end
  g = a;
end

gcd_fn(252, 105)   % 21

Anonymous functions

@(params) expr creates an anonymous function (lambda):

sq  = @(x) x ^ 2;
hyp = @(a, b) sqrt(a^2 + b^2);

sq(7)       % 49
hyp(3, 4)   % 5

Lexical capture

A lambda captures the value of free variables at definition time:

rate = 0.05;
interest = @(p, n) p * (1 + rate) ^ n;

interest(1000, 10)   % 1628.89  (uses captured rate = 0.05)

rate = 0.99;         % does not affect the already-created lambda
interest(1000, 10)   % still 1628.89

Passing functions as arguments

Use @name to pass an existing function, or @(x) expr inline:

function s = midpoint(f, a, b, n)
  h = (b - a) / n;
  s = 0;
  for k = 1:n
    s += f(a + (k - 0.5) * h);
  end
  s *= h;
end

midpoint(@(x) x^2,    0, 1, 1000)    % ≈ 0.333333  (∫₀¹ x² dx)
midpoint(@(x) sin(x), 0, pi, 1000)   % ≈ 2.000001  (∫₀ᵖⁱ sin x dx)

Functions returning functions

function f = make_adder(c)
  f = @(x) x + c;
end

add5  = make_adder(5);
add10 = make_adder(10);

add5(3)         % 8
add10(7)        % 17
add5(add10(1))  % 16

Documentation comments

Place %-prefixed lines immediately after the function header to document a function (MATLAB H1-line convention). The REPL command help <name> displays them:

function t = tri(n)
% Return the nth triangular number T(n) = n*(n+1)/2.
% Usage: t = tri(n)
%
% Example:
%   tri(4)  →  10
  t = n * (n + 1) / 2;
end
>> help tri
Return the nth triangular number T(n) = n*(n+1)/2.
Usage: t = tri(n)

Example:
  tri(4)  →  10
  • Any number of consecutive % lines form the doc block.
  • A blank line between the function header and the first % breaks the association — only lines that immediately follow the header are collected.
  • One leading space after % is stripped; remaining indentation is preserved, so % example displays as example.
  • #-style comments work the same way.
  • help <name> works for autoloaded functions on the path before the first call — ccalc loads the file on demand to extract the doc.

Function hoisting in scripts

In a script file (one that does not begin with a function definition), helper functions may be placed anywhere in the file — including after the code that calls them. ccalc pre-registers all top-level function definitions before executing the script body, matching MATLAB/Octave script semantics:

% main code at the top — calls a helper defined further down
result = double_it(7);
fprintf("double_it(7) = %d\n", result);   % prints: double_it(7) = 14

% helper function at the bottom
function y = double_it(x)
  y = x * 2;
end

This is the standard layout for MATLAB/Octave scripts: keep the main logic at the top and put helper functions at the bottom, where they are out of the way.

REPL difference: In the interactive REPL, functions take effect immediately when entered — a function must be defined before its first call.


Function files and autoload

A .calc (or .m) file that begins with a function definition is a function file. ccalc handles it differently from a script:

  • Only the primary function (the first one) is exposed to the caller’s workspace.
  • Any additional functions in the file are local helpers — invisible outside the file, but available to the primary function (MATLAB-style scoping).
  • When a function name is called that is not in the workspace, ccalc automatically searches for <name>.calc / <name>.m on the current directory and the session path, loads it, and calls it — no explicit source() required.
% bisect.calc — primary function + private helper
function [c, k] = bisect(fun, a, b, tol)
% help text goes here, right after the function line
  steps = ceil(log2((b - a) / tol));
  [c, k] = bisect_r(fun, a, b, 0, steps);   % calls local helper
end

function [c, k] = bisect_r(fun, a, b, k, maxSteps)
  % bisect_r is local — not visible outside bisect.calc
  ...
end

If bisect.calc is on the path, calling bisect(...) without any source() works automatically:

[c, k] = bisect(@(x) x^2 - 2, 1, 2, 1e-8)   % bisect.calc auto-loaded

source('bisect.calc') still works for explicit loading.


Testing with assert

assert checks a condition and throws an error if it is false. Use it in scripts and function files to catch programming mistakes early.

% assert(cond) — error if cond is 0, NaN, or empty
assert(1 == 1)            % passes — silently returns
assert(2 > 3)             % fails: "assert: condition is false"

% assert(expected, actual) — error if values differ (element-wise)
assert(sqrt(4), 2)        % passes
assert([1 2], [1 3])      % fails: "assert: values differ"

% assert(expected, actual, tol) — error if |expected - actual| > tol
assert(pi, 3.14159, 1e-4) % passes — within tolerance
assert(pi, 3.14, 1e-4)    % fails — difference 0.00159 > 1e-4

assert works on scalars, vectors, and matrices. For numeric comparisons the element-wise absolute difference is checked; for matrices the check applies to every element.


Full example

ccalc examples/user_functions.calc

See also: help userfuncs for the in-REPL reference, and Control Flow for if, for, while, break, and return.