Tradfns#

Tradfns are the original way to write your own functions in APL. Tradfns are procedural in style, unlike dfns, which are functional.

The basic structure of a tradfn is:

 header line
function body

Function body#

Control structures#

Let’s consider the body first. We have available to us the full set of control structures from procedural languages. All such key words begin with a colon, :, for example If :EndIf. Lines with such keywords must begin with the keyword, and have nothing else on them, although parameters (like a condition) are considered parenthesised expressions. For example,

 Ex ;i;j;k
  :For i j k :In 'abc'(1 2 3)'ABC'
      i j k
  :EndFor

Ex
abc
1 2 3
ABC

This assigns (i j k)←'abc' during the first loop, then (i j k)←1 2 3, etc. :For can also “transpose”, using :InEach instead of :In which makes (i j k)←'a'1'A' etc:

 Ex ;i;j;k
  :For i j k :InEach 'abc'(1 2 3)'ABC'
      i j k
  :EndFor

Ex
a 1 A
b 2 B
c 3 C

Any unpacking is possible, for example:

 Ex ;i;j;k
  :For i(j k) :InEach (3)('aA' 'bB' 'cC')
      i j k
  :EndFor

Ex
1 aA
2 bB
3 cC

:If, of course, has :Else, but also :ElseIf. While and are normal arithmetic functions, it is allowed to write one or more :AndIfs or :OrIfs which will shortcut. A quite common pattern used to check if a variable exists and then, for example, set it to a default value if it doesn’t:

 Ex ;state
  :If 0=⎕NC'state'
      state42
  :EndIf
  state

Ex
42

Ambivalence#

While dfns are always ambivalent (though will give value error if called monadically and there’s no ⍺← statement), Dyalog tradfns have to be explicitly declared ambivalent in the header: ∇result←{lAarg} FnName rArg. Then one can test for ⎕NC'lArg', but there’s also a faster way: 900⌶ which ignores its argument and returns whether the function was called monadically:

 res{lArg} Ambiv rArg
  :If 900
      lArg42
  :EndIf
  reslArg ⍝ Return the left argument

Ambiv 'hello'
99 Ambiv 'world'
42
99

Note that 900⌶ only works for tradfns, although dfns don’t need it so much since they have ⍺←.

Advanced control structures#

:If and :While should feel familiar, but the :Select statement warrants specification:

:Select expression
:Case value
:CaseList values
:Else
:EndSelect

No need “break”, like in C’s switch statement. It jumps to the end when reaching the next case.

The conditional loops are a bit interesting in that you can piece them together as you want. You can begin with either :While condition (which checks before it starts) or :Repeat which doesn’t check. You can end with either :EndWhile/:EndRepeat (which don’t check anything) or :Until condition (which does). In other words, you can match :While with :Until. :While and :Until can also be followed by one or more :AndIfs or :OrIfs.

You can even insert statements between :If/:ElseIf/:While/:Until and :AndIf/:OrIf, but this can be hard to read. For example, consider the following:

 rFoo val;b
  b1
  :If 10<val
      b2
  :AndIf 100>val
      rb,val
  :Else
      rval,b
  :EndIf

Foo 5
Foo 50
Foo 500
5 1
2 50
500 2

The :AndIf and :OrIf allows you to build up Boolean expressions that have the same kind of short-circuiting behaviour as that found in mainstream languages, but with the added option of statements between them. Whilst this can be confusing to read, it has its place, for example, where you have some costly set-up code required in order to evaluate one of the expressions making up a boolean condition in an if-statement. You can do work that needs to be prepared so we’re ready to do the next check. For example,

:If ⎕NEXISTS file
    content⎕NGET file 1
:AndIf ×≢content
    Process¨content
:EndIf

That sort of thing would be painful to write in as a dfn.

You can do the same with loops, too:

 rFoo val
  rval
  :Repeat
      r+?5
  :Until r>11
  :OrIf r=9

Foo 1
Foo ¯100
12
13

When looping, you can also continue with the next iteration without finishing this one, by stating :Continue and you can quit the loop immediately with :Leave:

 rFoo
  r0
  :While 1
      r+1
      :If r>10
          :Leave     ⍝ Like 'break' in C or Python
      :EndIf
  :EndWhile

Foo
11

Non-flow structures#

There’s actually another couple of interesting structures, which aren’t really flow control per se. :Section…:EndSection is like :If 1 which is useful for organising your code, and they don’t need a comment symbol on their right. You can put any text there. The :Section itself provides no actual visible functionality.

 rFoo arg
  rarg
  :Section We can group code that belongs together in sections
      :If r>10
          'Greater than 10'
      :EndIf
  :EndSection

Foo 4
Foo 15
4
Greater than 10
15

:Trap takes one or more error numbers exactly like dfns’ error guards. Then the main code, and then :Case or :CaseList with error numbers. You can also/instead use :Else for all (other) errors.

Tradfns can also do advanced stuff that dfns can’t do. If you write :Implements trigger var then the function gets called every time var is changed in that namespace.

 rFoo
  :Implements trigger var
  'var changed!'

var0
var changed!

If you want a callback on all variable changes, you can use * instead of a name. You can also use var1,var2 to only react to those. :Implements is just a declaration, not a structure.

The header#

There can be up to four parts of the header:

  • result

  • calling syntax

  • locals

  • comment

Result#

The result is optional and must be terminated by if present. It contains the result name or a parenthesised list of space-separated names.

If one needs to return a vector of various values, then using a name list is nice, because one can assign to each name separately, and only upon return are they collected together:

(vertices results)
  vertices
  results

Fun fact: a name can occur multiple places in the header, including in a single name list, so you can actually write somewhat useful function without any body, just a header. For example, ∇(x x)←dup x makes two of its argument. And (x y)←x juxtapose y is the same as {⍺ ⍵}.

The result can also be made “shy”, like a dfn that ends with an assignment {shh←42}. This is done by putting the name or the name list in braces. For example, ∇{shh}←Shy shh will silence its argument, but the value can still be coerced out.

If the result variable name is a function, then the function will return that function! Behold:

 FnPlusMinus
 :If 1=?2
     Fn+
 :Else
     Fn-
 :EndIf

Then 3 PlusMinus 4 will give either ¯1 or 7, each time it is run, it is random.

3 PlusMinus 4
3 PlusMinus 4
3 PlusMinus 4
¯1
7
¯1

Calling syntax#

The calling syntax of the header is always be present. It is basically an image of how the function needs to be called. For example, a monadic function would have FunctionName argumentName. A dyadic function would have leftArg FnName rightArg. The right argument can also be a name list like the result. In that case, APL will refuse to call the function with anything but a vector argument of the correct length. This is pretty neat for “type” checking. A tradfn can be made ambivalent by putting braces around the left argument name, as we discussed before. The left and right arguments are not allowed to be the same, but multiple names in the right argument can be the same (last will prevail) which is convenient if you’re writing a function that needs to take multiple arguments, some of which it doesn’t need, for example, foo(important _ critical _ _).

A tradfn can be also be niladic, unlike a dfn. Then the syntax part is just the function name. This is usually used for returning caches, bootstrapping, constants, etc. Another useful thing is for a niladic tradfn is to return a derived function, since that allows you to use the editor on it, and also to construct it over multiple lines.

 fAvg
  sum+
  count1⌈≢
  fsum÷count

So, about operators. The “central” part of the syntax declaration for an operator needs to be parenthesised. It then has two names for a monadic operator (Operand OPERATOR) or three names for a dyadic operator (Operand1 OPERATOR Operand2). Outside the parenthesis there must be a name or namelist on the right for the right argument(s), and optionally an optionally optional left argument on the left. In other words, that is either no left argument or yes a left argument or a braced left argument.

Now we can also understand why allowing a left argument namelist would make it really hard to understand what the header stood for: things like (a b)(c d) and (a b c)d e would certainly be tougher to parse for humans. In practice, if multiple “arguments” are needed, people tend to use multiple right arguments. Of course, you can always unpack any array into any structure, not just a simple list.

As opposed to dfns, tradfns do not auto-localise. This means that it is important that you do so by declaring all your locals. After the syntax part, one can write one or more names, each prefixed by ; to localise them. There’s no need to localise other names that occur in the header. They’re all local. The only exception is the function/operator’s own name. If you really want to reuse that name, you can localise it explicitly. As a relatively new feature (17.0), you can continue localising names up until you have any actual code (so comments and empty lines are fine):

foo;local
;more;locals
⍝ finally:
;last;ones

Finally, the header line allows a comment. Nothing fancy there. Just a comment :-)

So in summary:

∇{(result1 result2)}←{left}(Op1 OP Op2)(right args);local;local2 comment