Writing and Verifying Inline Dfns in Dyalog APL Without Leaking Variables
How to write, apply, and verify inline dfns in Dyalog APL — argument binding with ⍺ and ⍵, scope isolation checks with ⎕NC, and how to avoid corrupting globals.
30 Apr 2026, 10:31 UTC

You want to pass a small one-off function to an operator like ¨ or / without cluttering the workspace with a named tradfn. A dfn — an inline function written in braces — is the right tool, but two things trip people up: getting the ⍺/⍵ argument binding right, and accidentally writing to globals from inside the braces. This guide covers both, with checks you can run in a fresh session.
What a dfn gives you
A dfn is an anonymous function delimited by { and }. The right argument is always ⍵; in a dyadic call the left argument is ⍺. The result is the value of the last expression that is not an assignment. Dfn syntax has been stable since the early Dyalog versions, so everything here applies to any reasonably current release (the examples assume Dyalog 18.x with default ⎕IO←1 and ⎕ML←1).
The key engineering property is scope: a dfn creates its own local lexical scope. Names assigned inside it do not appear in the calling namespace. That makes dfns safe building blocks for pipelines — as long as you do not deliberately reach outward.
Prerequisites
- A Dyalog session (IDE or
dyalogterminal). No special permissions are needed; everything runs at the session prompt. - Default session settings, or at least knowledge of your
⎕IO(index origin) and⎕ML(migration level), because index-sensitive primitives inside the dfn obey the caller's settings.
Step 1: Confirm basic evaluation
At the session prompt, run:
{⍵+1} 5
6The result must be 6. This confirms monadic evaluation: ⍵ was bound to 5 and the final expression was returned.
For the dyadic form:
3 {⍺+⍵} 4
7Here ⍺ is 3 and ⍵ is 4. If you call a dfn that references ⍺ monadically, you get a SYNTAX ERROR (valence error) — a common first mistake when refactoring a dyadic dfn into a pipeline that only supplies one argument.
Step 2: Use a dfn with an operator
The most common task: apply a small transformation across data without naming a function. Example — keep the sign of each of the first five integers:
{×⍵}¨⍳5
1 1 1 1 1And a filtering example with a dyadic dfn under reduction:
{⍺⌈⍵}/3 1 4 1 5
5This computes the maximum by repeatedly taking the larger of the accumulator (⍺) and the next item (⍵). The dfn slots into / and ⍤ exactly where a named function would — no syntactic adaptation needed.
Step 3: Verify scope isolation
Assign a name inside a dfn, then check the namespace:
{a←2 ⋄ ⍵×a} 3
6
⎕NC 'a'
0⎕NC 'a' must return 0, meaning the name is undefined in the current namespace. The assignment a←2 lived and died inside the dfn. This is the behavior that makes dfns composable: you can reuse throwaway names like a or t without auditing the workspace first.
Step 4: Handle the global-write hazard deliberately
The isolation guarantee has a hole if you exploit dynamic scoping: if a name already exists as a global, assigning to it inside a dfn can modify the caller's variable rather than create a local, depending on how the name was introduced. In session-based work this is a real corruption risk — a "helper" dfn can silently overwrite a global the rest of your code depends on.
Practical rules:
- Never assign to names inside a dfn unless you created them in that dfn's body.
- If a function genuinely must produce a side effect or export a result, wrap it as a tradfn (
∇) with an explicit result and localized header, where intent is visible. - Avoid legacy system-dependent code (
⎕WI,⎕SM) inside dfns; under newer sandboxing and⎕SArestrictions (notably in 18.2) such code may behave differently or fail outright.
Checking a dfn against a tradfn
When refactoring a tradfn into a dfn, validate equivalence by running both on the same inputs:
∇ R←A Plus B
[1] R←A+B
∇
3 Plus 4
7
3 {⍺+⍵} 4
7Matching results across a representative set of inputs (including edge cases like empty arrays and negative numbers) is your practical equivalence check. Also test under the ⎕IO your production code uses if the function touches ⍳, ⍸, or indexing.
Limitations
- Multi-line dfns with guards (
:) and diamond separators work, but anything beyond a handful of lines is usually clearer as a named function with⎕CR-inspectable source. - Behavior of name-class checks (
⎕NC) and cross-referencing around local variables has differed in versions before 16.0; if you maintain legacy code, re-run the Step 3 check on your target version. - Dfns cannot be recursive by name; use
∇inside the dfn for self-reference.
None of these operations change workspace state, so no rollback is needed — )CLEAR gives you a clean session if your experiments get messy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.