Tacit programming: Difference between revisions

From APL Wiki
Jump to navigation Jump to search
(Added blog post on understanding trains.)
m (Fix code)
 
(18 intermediate revisions by 5 users not shown)
Line 1: Line 1:
'''Tacit programming''', also called '''[[wikipedia:Tacit_programming|point-free style]]''', refers to usage of tacit [[function]]s that are defined in terms of implicit [[argument]]s. This is in contrast to the explicit use of arguments in [[dfn]]s (<source inline lang=apl>⍺ ⍵</source>) and [[tradfn]]s (which have named arguments). Some APL dialects allow to combine functions into [[#trains|trains]] following a small set of rules. This allows creating complex [[derived function]]s without specifying any arguments explicitly.
[[File:Function compositions.png|thumb|right|Diagrams of [[function composition]]s, an important part of tacit programming.]]
'''Tacit programming''', also called '''[[wikipedia:Tacit_programming|point-free style]]''', refers to usage of tacit [[function]]s that are defined in terms of implicit [[argument]]s. This is in contrast to the explicit use of arguments in [[dfn]]s (<syntaxhighlight inline lang=apl>⍺ ⍵</syntaxhighlight>) and [[tradfn]]s (which have named arguments). Some APL dialects allow to combine functions into [[train]]s following a small set of rules. This allows creating complex [[derived function]]s without specifying any arguments explicitly.


Dialects which implement trains include [[Dyalog APL]], [[dzaima/APL]], [[ngn/apl]] and [[NARS2000]].
Dialects which implement trains include [[Dyalog APL]], [[dzaima/APL]], [[ngn/apl]] and [[NARS2000]].
Line 5: Line 6:
== Primitives ==
== Primitives ==
All [[primitive functions]] are tacit. Some APLs allow primitive functions to be named.
All [[primitive functions]] are tacit. Some APLs allow primitive functions to be named.
<source lang=apl>
<syntaxhighlight lang=apl>
       plus ← +
       plus ← +
       times ← ×
       times ← ×
       6 times 3 plus 5
       6 times 3 plus 5
48
48
</source>
</syntaxhighlight>


== Derived functions ==
== Derived functions ==
Functions derived from a monadic operator and an operand, or from a dyadic operator and two operands are tacit functions:
Functions derived from a monadic operator and an operand, or from a dyadic operator and two operands are tacit functions:
<source lang=apl>
<syntaxhighlight lang=apl>
       Sum ← +/
       Sum ← +/
       Sum ⍳10
       Sum ⍳10
Line 20: Line 21:


       Dot ← +.×
       Dot ← +.×
       3 1 4 dot 2 7 1
       3 1 4 Dot 2 7 1
17
17
</source>
</syntaxhighlight>
 
== Derived operators ==
== Derived operators ==
A dyadic operator with its right operand forms a tacit monadic operator:
A dyadic operator with its right operand forms a tacit monadic operator:
<source lang=apl>
<syntaxhighlight lang=apl>
       1(+⍣2)10
       1(+⍣2)10
12
12
Line 31: Line 33:
       1 +Twice 10
       1 +Twice 10
12
12
</source>
</syntaxhighlight>


== Trains ==
== Trains ==
A train is a series of functions in isolation. An isolated function is either surrounded by parentheses or named. Below, <source lang=apl inline>⍺</source> and <source lang=apl inline>⍵</source> refer to the arguments of the train. <source lang=apl inline>f</source>, <source lang=apl inline>g</source>, and <source lang=apl inline>h</source> are functions (which themselves can be tacit or not), and <source lang=apl inline>A</source> is an array. The arguments are processed by the following rules:
A [[train]] is a series of functions in isolation. An isolated function is either surrounded by parentheses or named.


=== 3-trains ===
These rules are used for 3-trains, called [[fork]]s:
A 3-train is a ''fork'', so denoted because its structure resembles a three-tines fork, or a three-pronged pitchfork. The two outer functions are applied first, and their results are used as arguments to the middle function:
{|
{|
|<source lang=apl>  (f g h) ⍵</source>|| {{←→}} ||<source lang=apl>(  f ⍵) g (  h ⍵)</source>
|<syntaxhighlight lang=apl>  (f g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>(  f ⍵) g (  h ⍵)</syntaxhighlight>
|-
|-
|<source lang=apl>⍺ (f g h) ⍵</source>|| {{←→}} ||<source lang=apl>(⍺ f ⍵) g (⍺ h ⍵)</source>
|<syntaxhighlight lang=apl>⍺ (f g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>(⍺ f ⍵) g (⍺ h ⍵)</syntaxhighlight>
|}
|}
The ''left tine'' of a fork can be an array:
The ''left tine'' of a fork can be an array:
{|
{|
|<source lang=apl>  (A g h) ⍵</source>|| {{←→}} ||<source lang=apl>A g (  h ⍵)</source>
|<syntaxhighlight lang=apl>  (A g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>A g (  h ⍵)</syntaxhighlight>
|-
|-
|<source lang=apl>⍺ (A g h) ⍵</source>|| {{←→}} ||<source lang=apl>A g (⍺ h ⍵)</source>
|<syntaxhighlight lang=apl>⍺ (A g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>A g (⍺ h ⍵)</syntaxhighlight>
|}
|}


=== 2-trains ===
In APL (but not [[J]]), these rules are used for 2-trains, called [[atop]]s:
Most dialects define a 2-train is an ''atop'', equivalent to the function derived using the [[Atop (operator)|Atop]] operator. The left function is applied [[monadic function|monadically]] on the result of the right function:
{|
{|
|<source lang=apl>  (g h) ⍵</source>|| {{←→}} ||<source lang=apl>g (  h ⍵)</source>
|<syntaxhighlight lang=apl>  (g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>g (  h ⍵)</syntaxhighlight>
|-
|-
|<source lang=apl>⍺ (g h) ⍵</source>|| {{←→}} ||<source lang=apl>g (⍺ h ⍵)</source>
|<syntaxhighlight lang=apl>⍺ (g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>g (⍺ h ⍵)</syntaxhighlight>
|}
|}


Only [[dzaima/APL]] allows <source lang=apl inline>(A h)</source>, which it treats as <source lang=apl inline>A∘h</source>.<ref>dzaima/APL: [https://github.com/dzaima/APL/blob/ceea05e25687988ed0980a4abf4b9249b736543f/docs/differences.txt#L19 Differences from Dyalog APL]. Retrieved 09 Jan 2020.</ref> See [[Bind]].
Any train can be expressed in terms of [[function composition]] &mdash; except dyadic forks. Some common patterns are:


[[J]] instead defines the 2-train as a [[hook]], equivalent to the function derived using the [[Withe]] operator. The left function is always applied [[dyadic function|dyadically]], taking as right argument, the result of applying the right function on the right argument. If there is no left argument, the sole argument is used also as left argument:
{|
{|
|<source lang=apl> (g h) ⍵</source>|| {{←→}} ||<source lang=apl>⍵ g (h )</source>
|<syntaxhighlight lang=apl>(f g h) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>g⍨∘f⍨∘h⍨ </syntaxhighlight>
|-
|<syntaxhighlight lang=apl>(f g f) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>g⍥f⍨ ⍵</syntaxhighlight>
|-
|-
|<source lang=apl>(g h) ⍵</source>|| {{←→}} ||<source lang=apl>⍺ g (h )</source>
|<syntaxhighlight lang=apl>(g f) ⍵</syntaxhighlight>|| {{←→}} ||<syntaxhighlight lang=apl>g∘f⍨ ⍵</syntaxhighlight>
|}
|}
=== Problems caused by function-operator overloading ===
Trains that use a [[Function-operator_overloading|hybrid function-operator]] in its [[function]] role can run into the problems with the hybrid being parsed as a monadic [[operator]] instead of as a function. This happens when a function appears to the immediate left of the hybrid, causing this function to be bound as the hybrid's operand — the hybrid taking on an operator role — rather than supplying a left [[argument]] or post-processing the result.
For example, the attempted [[#3-trains|fork]] <source lang=apl inline>f/h</source> is actually parsed as the [[#2-trains|atop]] <source lang=apl inline>(f/)h</source> and the attempted atop <source lang=apl inline>f/</source> is actually parsed as a [[Windowed Reduce|Windowed Reduction]]. There are multiple [[Function-operator_overloading#Mitigation|ways to mitigate this issue]]. For example, the fork can be enforced using the [[Atop (operator)|Atop operator]] by applying identity to the hybrid's result as <source lang=apl inline>f⊢⍤/h</source> and the atop can be enforced by using the explicit Atop operator instead of a 2-train; <source lang=apl inline>f⍤/</source>.
No problem presents when left argument is supplied as an array (literal or by name reference) and when the hybrid is the leftmost token. For example, <source lang=apl inline>1 0 1/⌽</source> and <source lang=apl inline>/,⊃</source> are parsed as forks.


== Debugging ==
== Debugging ==
In [[Dyalog APL]], analysis of trains is assisted by a [[user command]] <source lang=apl inline>]Boxing on</source>. This is achieved by executing the command <source lang=apl inline>]Boxing on</source> and then entering a train without any parameters. A structure of the train will be displayed.
In [[Dyalog APL]], analysis of trains is assisted by a [[user command]] <syntaxhighlight lang=apl inline>]Boxing on</syntaxhighlight>. This is achieved by executing the command <syntaxhighlight lang=apl inline>]Boxing on</syntaxhighlight> and then entering a train without any parameters. A structure of the train will be displayed.


For example, the "accursed train" from the section below can be analysed like this:
For example, the "accursed train" from the section below can be analysed like this:
<source lang=apl>
<syntaxhighlight lang=apl>
       ]Boxing on
       ]Boxing on
Was OFF
Was OFF
Line 92: Line 87:
│└───────────┴─────────────────┘│      │
│└───────────┴─────────────────┘│      │
└───────────────────────────────┴───────┘
└───────────────────────────────┴───────┘
</source>
</syntaxhighlight>


Alternatively, a train can be represented in form of a tree:
Alternatively, a train can be represented in form of a tree:
<source lang=apl>
<syntaxhighlight lang=apl>
       ]Boxing on -trains=tree
       ]Boxing on -trains=tree
Was ON -trains=box
Was ON -trains=box
Line 107: Line 102:
+ ×  ┌┴┐       
+ ×  ┌┴┐       
       ∘ ×       
       ∘ ×       
</source>
</syntaxhighlight>
Or fully parenthesised:
Or fully parenthesised:
<source lang=apl>
<syntaxhighlight lang=apl>
       ]Boxing on -trains=parens
       ]Boxing on -trains=parens
Was OFF -trains=box
Was OFF -trains=box
       ((+.×⍨⊢~∘.×⍨)1↓⍳)    ⍝ the train to be analysed
       ((+.×⍨⊢~∘.×⍨)1↓⍳)    ⍝ the train to be analysed
(((+.×)⍨)(⊢~((∘.×)⍨)))(1↓⍳)
(((+.×)⍨)(⊢~((∘.×)⍨)))(1↓⍳)
</source>
</syntaxhighlight>


=== Conversion to dfns ===
It can help understanding to convert a tacit function to a dfn. For many tacit functions, it is not immediately clear if the intention of the function is to be used monadically or dyadically, or even both. Such knowledge can be conveyed by comments, but sometimes it is possible to spot patterns that are exclusively monadic or dyadic: A function with a bound argument (for example <syntaxhighlight lang=apl inline>+∘1</syntaxhighlight>) can indicate a monadic function, and in some contexts, <syntaxhighlight lang=apl inline>=</syntaxhighlight>, which can only be used dyadically, would indicate a dyadic function. The website [https://tacit.help tacit.help] provides automated translation of most tacit functions, into both monadic and dyadic, fully parenthesised dfns.
== Examples ==
== Examples ==
One of the major benefits of tacit programming is the ability to convey a short, well-defined idea as an isolated expression. This aids both human readability ([[semantic density]]) and the computer's ability to interpret code, potentially executing special code for particular [[idiom]]s.
One of the major benefits of tacit programming is the ability to convey a short, well-defined idea as an isolated expression. This aids both human readability ([[semantic density]]) and the computer's ability to interpret code, potentially executing special code for particular [[idiom]]s.


=== Plus and minus ===
=== Plus and minus ===
<source lang=apl>
<syntaxhighlight lang=apl>
       (+,-) 2    ⍝ ±2
       (+,-) 2    ⍝ ±2
2 ¯2
2 ¯2
       5 (+,-) 2  ⍝ 5±2
       5 (+,-) 2  ⍝ 5±2
7 3
7 3
</source>
</syntaxhighlight>


=== Arithmetic mean ===
=== Arithmetic mean ===
<source lang=apl>
<syntaxhighlight lang=apl>
       (+⌿÷≢) ⍳10      ⍝ Mean of the first ten integers
       (+⌿÷≢) ⍳10      ⍝ Mean of the first ten integers
5.5
5.5
       (+⌿÷≢) 5 4⍴⍳4    ⍝ Mean of columns in a matrix
       (+⌿÷≢) 5 4⍴⍳4    ⍝ Mean of columns in a matrix
1 2 3 4
1 2 3 4
</source>
</syntaxhighlight>


=== Fractions ===
=== Fractions ===
We can convert decimal numbers to fractions. For example, we can convert <math>2.625</math> to the improper fraction <math>\tfrac{21}{8}</math> with
We can convert decimal numbers to fractions. For example, we can convert <math>2.625</math> to the improper fraction <math>\tfrac{21}{8}</math> with
<source lang=apl>
<syntaxhighlight lang=apl>
       (1∧⊢,÷)2.625
       (1∧⊢,÷)2.625
21 8
21 8
</source>
</syntaxhighlight>
Alternatively, we can convert it to the mixed fraction <math>2\tfrac{5}{8}</math> with a mixed fraction:
Alternatively, we can convert it to the mixed fraction <math>2\tfrac{5}{8}</math> with a mixed fraction:
<source lang=apl>
<syntaxhighlight lang=apl>
       (1∧0 1∘⊤,÷)2.625
       (1∧0 1∘⊤,÷)2.625
2 5 8
2 5 8
</source>
</syntaxhighlight>


=== Is it a palindrome? ===
=== Is it a palindrome? ===
<source lang=apl>
<syntaxhighlight lang=apl>
       (⌽≡⊢)'racecar'
       (⌽≡⊢)'racecar'
1
1
       (⌽≡⊢)'racecat'
       (⌽≡⊢)'racecat'
0
0
</source>
</syntaxhighlight>


=== Split delimited text ===
=== Split delimited text ===
<source lang=apl>
<syntaxhighlight lang=apl>
       ','(≠⊆⊢)'comma,delimited,text'
       ','(≠⊆⊢)'comma,delimited,text'
┌─────┬─────────┬────┐
┌─────┬─────────┬────┐
Line 165: Line 162:
│space│delimited│text│
│space│delimited│text│
└─────┴─────────┴────┘
└─────┴─────────┴────┘
</source>
</syntaxhighlight>


=== Component of a vector in the direction of another vector ===
=== Component of a vector in the direction of another vector ===
Sometimes a train can make an expression nicely resemble its equivalent definition in traditional mathematical notation. As an example, here is a program to compute the component of a vector <math>\textbf{a}</math> in the direction of another vector <math>\textbf{b}</math>:
Sometimes a train can make an expression nicely resemble its equivalent definition in traditional mathematical notation. As an example, here is a program to compute the component of a vector <math>\textbf{a}</math> in the direction of another vector <math>\textbf{b}</math>:
:::<math>\textbf{a}_\textbf{b} = (\textbf{a}\cdot\hat{\textbf{b}})\hat{\textbf{b}}</math>
:::<math>\textbf{a}_\textbf{b} = (\textbf{a}\cdot\hat{\textbf{b}})\hat{\textbf{b}}</math>
<source lang=apl>
<syntaxhighlight lang=apl>
       Root ← *∘÷⍨              ⍝ Nth root
       Root ← *∘÷⍨              ⍝ Nth root
       Norm ← 2 Root +.×⍨      ⍝ Magnitude (norm) of numeric vector in Euclidean space
       Norm ← 2 Root +.×⍨      ⍝ Magnitude (norm) of numeric vector in Euclidean space
Line 177: Line 174:
       3 5 2 InDirOf 0 0 1      ⍝ Trivial example
       3 5 2 InDirOf 0 0 1      ⍝ Trivial example
0 0 2
0 0 2
</source>
</syntaxhighlight>
For a more parallel comparison of the notations, see the [[Comparison_with_traditional_mathematics#Practical_example|comparison with traditional mathematics]].
For a more parallel comparison of the notations, see the [[Comparison_with_traditional_mathematics#Practical_example|comparison with traditional mathematics]].


===The Number of the Beast===
===The Number of the Beast===
The following expression for computing the [[wikipedia:666 (number)|number of the Beast]] (and of [[I.P. Sharp]]'s APL-based email system, [[666 BOX]]) nicely illustrates how to read a train.
The following expression for computing the [[wikipedia:666 (number)|number of the Beast]] (and of [[I.P. Sharp]]'s APL-based email system, [[666 BOX]]) nicely illustrates how to read a train.
<source lang=apl>
<syntaxhighlight lang=apl>
       ((+.×⍨⊢~∘.×⍨)1↓⍳)17 ⍝ Accursed train
       ((+.×⍨⊢~∘.×⍨)1↓⍳)17 ⍝ Accursed train
666
666
</source>
</syntaxhighlight>
First, <source lang=apl inline>((+.×⍨⊢~∘.×)1↓⍳)</source> is supplied with only one argument <source lang=apl inline>17</source> and is thus interpreted monadically.
First, <syntaxhighlight lang=apl inline>((+.×⍨⊢~∘.×)1↓⍳)</syntaxhighlight> is supplied with only one argument <syntaxhighlight lang=apl inline>17</syntaxhighlight> and is thus interpreted monadically.


Second, <source lang=apl inline>(+.×⍨⊢~∘.×⍨)1↓⍳</source> is a 4-train: reading right-to-left, the last 3 components are interpreted as the fork <source lang=apl inline>1↓⍳</source> and the 4-train is interpreted as the atop <source lang=apl inline>(+.×⍨⊢~∘.×⍨)(1↓⍳)</source>.
Second, <syntaxhighlight lang=apl inline>(+.×⍨⊢~∘.×⍨)1↓⍳</syntaxhighlight> is a 4-train: reading right-to-left, the last 3 components are interpreted as the fork <syntaxhighlight lang=apl inline>1↓⍳</syntaxhighlight> and the 4-train is interpreted as the atop <syntaxhighlight lang=apl inline>(+.×⍨⊢~∘.×⍨)(1↓⍳)</syntaxhighlight>.
Similarly, <source lang=apl inline>(+.×⍨⊢~∘.×⍨)</source> is also a 4-train and interpreted as the atop <source lang=apl inline>+.×⍨(⊢~∘.×⍨)</source>.  
Similarly, <syntaxhighlight lang=apl inline>(+.×⍨⊢~∘.×⍨)</syntaxhighlight> is also a 4-train and interpreted as the atop <syntaxhighlight lang=apl inline>+.×⍨(⊢~∘.×⍨)</syntaxhighlight>.  


Thus the accursed train is interpreted as <source lang=apl inline>((+.×⍨(⊢~∘.×⍨))(1↓⍳))17</source>. Having read the train, we now evaluate it monadically.
Thus the accursed train is interpreted as <syntaxhighlight lang=apl inline>((+.×⍨(⊢~∘.×⍨))(1↓⍳))17</syntaxhighlight>. Having read the train, we now evaluate it monadically.
<source lang=apl>
<syntaxhighlight lang=apl>
       ((+.×⍨(⊢~∘.×⍨))(1↓⍳))17 ⍝ Accursed train as an atop over a fork atop a fork
       ((+.×⍨(⊢~∘.×⍨))(1↓⍳))17 ⍝ Accursed train as an atop over a fork atop a fork
       +.×⍨(⊢~∘.×⍨)1↓⍳17      ⍝ Atop evalution
       +.×⍨(⊢~∘.×⍨)1↓⍳17      ⍝ Atop evalution
Line 199: Line 196:
       +.×⍨2 3 5 7 11 13 17    ⍝ numbers 2 through 17 without those appearing in their multiplication table are primes
       +.×⍨2 3 5 7 11 13 17    ⍝ numbers 2 through 17 without those appearing in their multiplication table are primes
666                          ⍝ the sum of the squares of the primes up to 17
666                          ⍝ the sum of the squares of the primes up to 17
</source>
</syntaxhighlight>
Note that <source lang=apl inline>((⊢⍨∘.×⍨)1↓⍳)</source> is a train computing primes up to the given input.
Note that <syntaxhighlight lang=apl inline>((⊢~∘.×⍨)1↓⍳)</syntaxhighlight> is a train computing primes up to the given input.


A more satisfying variation of the accursed train is the following.
A more satisfying variation of the accursed train is the following.
<source lang=apl>
<syntaxhighlight lang=apl>
       (⍎⊢,⍕∘≢)'((+.×⍨⊢~∘.×⍨)1↓⍳)'                    ⍝ Accursed train 2.0
       (⍎⊢,⍕∘≢)'((+.×⍨⊢~∘.×⍨)1↓⍳)'                    ⍝ Accursed train 2.0
       ⍎(⊢,⍕∘≢)'((+.×⍨⊢~∘.×⍨)1↓⍳)'                    ⍝ 4-train intepreted as an atop
       ⍎(⊢,⍕∘≢)'((+.×⍨⊢~∘.×⍨)1↓⍳)'                    ⍝ 4-train intepreted as an atop
Line 210: Line 207:
       ⍎'((+.×⍨⊢~∘.×⍨)1↓⍳)17'                        ⍝ , evaluation
       ⍎'((+.×⍨⊢~∘.×⍨)1↓⍳)17'                        ⍝ , evaluation
666                                                  ⍝ ⍎ executes original Accursed train
666                                                  ⍝ ⍎ executes original Accursed train
</source>
</syntaxhighlight>
 
== See also ==
 
* [[Function composition]]


== External links ==
== External links ==
=== Tutorials ===
=== Tutorials ===
<div style="column-width:30em">
==== In text form ====
* [[Learning APL]]: [https://xpqz.github.io/learnapl/tacit.html Trainspotting]
* [[Learning APL]]: [https://xpqz.github.io/learnapl/tacit.html Trainspotting]
* [[Documentation_suites#Dyalog_APL|Dyalog documentation]]: [https://help.dyalog.com/16.0/Content/RelNotes14.0/Function%20Trains.htm version 14.0 release notes]
* [[Documentation_suites#Dyalog_APL|Dyalog documentation]]: [https://help.dyalog.com/16.0/Content/RelNotes14.0/Function%20Trains.htm version 14.0 release notes]
* [[Dfns workspace]]: [https://dfns.dyalog.com/n_tacit.htm Translation of <nowiki>[dfns]</nowiki> into tacit form]
* [[Dfns workspace]]: [https://dfns.dyalog.com/n_tacit.htm Translation of <nowiki>[dfns]</nowiki> into tacit form]
* [[Dfns workspace]]: [https://dfns.dyalog.com/n_dft.htm Display of function tree]
* [[Dfns workspace]]: [https://dfns.dyalog.com/n_fork.htm Simulation of fork syntax]
* [[APL Cultivation]]: [https://chat.stackexchange.com/rooms/52405/conversation/lesson-23-transcribing-to-and-reading-trains Transcribing to and reading trains]
* [[APL Cultivation]]: [https://chat.stackexchange.com/rooms/52405/conversation/lesson-23-transcribing-to-and-reading-trains Transcribing to and reading trains]
* [[APLtrainer]]: [https://www.youtube.com/watch?v=kt4lMZbn-so How to read trains in Dyalog APL code] (video)
* [[APLtrainer]]: [https://www.youtube.com/watch?v=A2LqqBosvY0 Function trains in APL] (video)
* [[Dyalog webinar]]: [https://www.youtube.com/watch?v=Enlh5qwwDuY?t=440 Train Spotting in Dyalog APL] (video)
* [[Dyalog '13]]: [https://www.youtube.com/watch?v=7-93GzDqC08 Train Spotting in Version 14.0] (video)
* gitonthescene: [https://gist.github.com/gitonthescene/666c77ee3ed0ae0a79cf8e057584b7fd Forks: Spoon fed]
* gitonthescene: [https://gist.github.com/gitonthescene/666c77ee3ed0ae0a79cf8e057584b7fd Forks: Spoon fed]
* gitonthescene: [https://gist.github.com/gitonthescene/5e9c25ab9edd2f2ce0d5ad38d8a8b2b4 Training day]
* gitonthescene: [https://gist.github.com/gitonthescene/5e9c25ab9edd2f2ce0d5ad38d8a8b2b4 Training day]
=== Documentation ===
 
* [https://help.dyalog.com/16.0/Content/RelNotes14.0/Function%20Trains.htm Announcement]
 
* [https://help.dyalog.com/latest/Content/Language/Introduction/Trains.htm Dyalog]
==== Videos ====
* [[APLtrainer]]: [https://www.youtube.com/watch?v=kt4lMZbn-so How to read trains in Dyalog APL code]
* [[APLtrainer]]: [https://www.youtube.com/watch?v=A2LqqBosvY0 Function trains in APL]
* [[Dyalog webinar]]: [https://www.youtube.com/watch?v=Enlh5qwwDuY?t=440 Train Spotting in Dyalog APL]
* [[Dyalog '13]]: [https://www.youtube.com/watch?v=7-93GzDqC08 Train Spotting in Version 14.0]
</div>


== References ==
== References ==

Latest revision as of 21:28, 6 March 2024

Diagrams of function compositions, an important part of tacit programming.

Tacit programming, also called point-free style, refers to usage of tacit functions that are defined in terms of implicit arguments. This is in contrast to the explicit use of arguments in dfns (⍺ ⍵) and tradfns (which have named arguments). Some APL dialects allow to combine functions into trains following a small set of rules. This allows creating complex derived functions without specifying any arguments explicitly.

Dialects which implement trains include Dyalog APL, dzaima/APL, ngn/apl and NARS2000.

Primitives

All primitive functions are tacit. Some APLs allow primitive functions to be named.

      plus ← +
      times ← ×
      6 times 3 plus 5
48

Derived functions

Functions derived from a monadic operator and an operand, or from a dyadic operator and two operands are tacit functions:

      Sum ← +/
      Sum ⍳10
55

      Dot ← +.×
      3 1 4 Dot 2 7 1
17

Derived operators

A dyadic operator with its right operand forms a tacit monadic operator:

      1(+⍣2)10
12
      Twice ← ⍣2
      1 +Twice 10
12

Trains

A train is a series of functions in isolation. An isolated function is either surrounded by parentheses or named.

These rules are used for 3-trains, called forks:

  (f g h) ⍵
(  f ⍵) g (  h ⍵)
⍺ (f g h) ⍵
(⍺ f ⍵) g (⍺ h ⍵)

The left tine of a fork can be an array:

  (A g h) ⍵
A g (  h ⍵)
⍺ (A g h) ⍵
A g (⍺ h ⍵)

In APL (but not J), these rules are used for 2-trains, called atops:

  (g h) ⍵
g (  h ⍵)
⍺ (g h) ⍵
g (⍺ h ⍵)

Any train can be expressed in terms of function composition — except dyadic forks. Some common patterns are:

(f g h) ⍵
g⍨∘f⍨∘h⍨ ⍵
(f g f) ⍵
g⍥f⍨ ⍵
(⊢ g f) ⍵
g∘f⍨ ⍵

Debugging

In Dyalog APL, analysis of trains is assisted by a user command ]Boxing on. This is achieved by executing the command ]Boxing on and then entering a train without any parameters. A structure of the train will be displayed.

For example, the "accursed train" from the section below can be analysed like this:

      ]Boxing on
Was OFF
      ((+.×⍨⊢~∘.×⍨)1↓⍳)     ⍝ the train to be analysed
┌───────────────────────────────┬───────┐
│┌───────────┬─────────────────┐│┌─┬─┬─┐│
││┌───────┬─┐│┌─┬─┬───────────┐│││1│↓│⍳││
│││┌─┬─┬─┐│⍨│││⊢│~│┌───────┬─┐│││└─┴─┴─┘│
││││+│.│×││ │││ │ ││┌─┬─┬─┐│⍨││││       │
│││└─┴─┴─┘│ │││ │ │││∘│.│×││ ││││       │
││└───────┴─┘││ │ ││└─┴─┴─┘│ ││││       │
││           ││ │ │└───────┴─┘│││       │
││           │└─┴─┴───────────┘││       │
│└───────────┴─────────────────┘│       │
└───────────────────────────────┴───────┘

Alternatively, a train can be represented in form of a tree:

      ]Boxing on -trains=tree
Was ON -trains=box
      ((+.×⍨⊢~∘.×⍨)1↓⍳)     ⍝ the train to be analysed
     ┌───┴───┐  
   ┌─┴─┐   ┌─┼─┐
   ⍨ ┌─┼─┐ 1 ↓ ⍳
 ┌─┘ ⊢ ~ ⍨      
 .     ┌─┘      
┌┴┐    .        
+ ×   ┌┴┐       
      ∘ ×

Or fully parenthesised:

      ]Boxing on -trains=parens
Was OFF -trains=box
      ((+.×⍨⊢~∘.×⍨)1↓⍳)     ⍝ the train to be analysed
(((+.×)⍨)(⊢~((∘.×)⍨)))(1↓⍳)

Conversion to dfns

It can help understanding to convert a tacit function to a dfn. For many tacit functions, it is not immediately clear if the intention of the function is to be used monadically or dyadically, or even both. Such knowledge can be conveyed by comments, but sometimes it is possible to spot patterns that are exclusively monadic or dyadic: A function with a bound argument (for example +∘1) can indicate a monadic function, and in some contexts, =, which can only be used dyadically, would indicate a dyadic function. The website tacit.help provides automated translation of most tacit functions, into both monadic and dyadic, fully parenthesised dfns.

Examples

One of the major benefits of tacit programming is the ability to convey a short, well-defined idea as an isolated expression. This aids both human readability (semantic density) and the computer's ability to interpret code, potentially executing special code for particular idioms.

Plus and minus

      (+,-) 2     ⍝ ±2
2 ¯2
      5 (+,-) 2   ⍝ 5±2
7 3

Arithmetic mean

      (+⌿÷≢) ⍳10       ⍝ Mean of the first ten integers
5.5
      (+⌿÷≢) 5 4⍴⍳4    ⍝ Mean of columns in a matrix
1 2 3 4

Fractions

We can convert decimal numbers to fractions. For example, we can convert to the improper fraction with

      (1∧⊢,÷)2.625
21 8

Alternatively, we can convert it to the mixed fraction with a mixed fraction:

      (1∧0 1∘⊤,÷)2.625
2 5 8

Is it a palindrome?

      (⌽≡⊢)'racecar'
1
      (⌽≡⊢)'racecat'
0

Split delimited text

      ','(≠⊆⊢)'comma,delimited,text'
┌─────┬─────────┬────┐
│comma│delimited│text│
└─────┴─────────┴────┘
      ' '(≠⊆⊢)'space delimited text'
┌─────┬─────────┬────┐
│space│delimited│text│
└─────┴─────────┴────┘

Component of a vector in the direction of another vector

Sometimes a train can make an expression nicely resemble its equivalent definition in traditional mathematical notation. As an example, here is a program to compute the component of a vector in the direction of another vector :

      Root ← *∘÷⍨              ⍝ Nth root
      Norm ← 2 Root +.×⍨       ⍝ Magnitude (norm) of numeric vector in Euclidean space
      Unit ← ⊢÷Norm            ⍝ Unit vector in direction of vector ⍵
      InDirOf ← (⊢×+.×)∘Unit   ⍝ Component of vector ⍺ in direction of vector ⍵
      3 5 2 InDirOf 0 0 1      ⍝ Trivial example
0 0 2

For a more parallel comparison of the notations, see the comparison with traditional mathematics.

The Number of the Beast

The following expression for computing the number of the Beast (and of I.P. Sharp's APL-based email system, 666 BOX) nicely illustrates how to read a train.

      ((+.×⍨⊢~∘.×⍨)1↓⍳)17 ⍝ Accursed train
666

First, ((+.×⍨⊢~∘.×)1↓⍳) is supplied with only one argument 17 and is thus interpreted monadically.

Second, (+.×⍨⊢~∘.×⍨)1↓⍳ is a 4-train: reading right-to-left, the last 3 components are interpreted as the fork 1↓⍳ and the 4-train is interpreted as the atop (+.×⍨⊢~∘.×⍨)(1↓⍳). Similarly, (+.×⍨⊢~∘.×⍨) is also a 4-train and interpreted as the atop +.×⍨(⊢~∘.×⍨).

Thus the accursed train is interpreted as ((+.×⍨(⊢~∘.×⍨))(1↓⍳))17. Having read the train, we now evaluate it monadically.

      ((+.×⍨(⊢~∘.×⍨))(1↓⍳))17 ⍝ Accursed train as an atop over a fork atop a fork
      +.×⍨(⊢~∘.×⍨)1↓⍳17       ⍝ Atop evalution
      +.×⍨(⊢1↓⍳17)~∘.×⍨1↓⍳17  ⍝ Fork evalution
      +.×⍨(1↓⍳17)~∘.×⍨1↓⍳17   ⍝ ⊢ evaluation
      +.×⍨2 3 5 7 11 13 17    ⍝ numbers 2 through 17 without those appearing in their multiplication table are primes
666                           ⍝ the sum of the squares of the primes up to 17

Note that ((⊢~∘.×⍨)1↓⍳) is a train computing primes up to the given input.

A more satisfying variation of the accursed train is the following.

      (⍎⊢,⍕∘≢)'((+.×⍨⊢~∘.×⍨)1↓⍳)'                    ⍝ Accursed train 2.0
      ⍎(⊢,⍕∘≢)'((+.×⍨⊢~∘.×⍨)1↓⍳)'                    ⍝ 4-train intepreted as an atop
      ⍎(⊢'((+.×⍨⊢~∘.×⍨)1↓⍳)'),⍕∘≢'((+.×⍨⊢~∘.×⍨)1↓⍳)' ⍝ fork evaluation
      ⍎'((+.×⍨⊢~∘.×⍨)1↓⍳)','17'                      ⍝ ⊢ evaluation and ⍕∘≢ evaluation
      ⍎'((+.×⍨⊢~∘.×⍨)1↓⍳)17'                         ⍝ , evaluation
666                                                  ⍝ ⍎ executes original Accursed train

External links

Tutorials

In text form


Videos

References


APL syntax [edit]
General Comparison with traditional mathematicsPrecedenceTacit programming (Train, Hook, Split composition)
Array Numeric literalStringStrand notationObject literalArray notation (design considerations)
Function ArgumentFunction valenceDerived functionDerived operatorNiladic functionMonadic functionDyadic functionAmbivalent functionDefined function (traditional)DfnFunction train
Operator OperandOperator valenceTradopDopDerived operator
Assignment MultipleIndexedSelectiveModified
Other Function axisBracket indexingBranchStatement separatorQuad nameSystem commandUser commandKeywordDot notationFunction-operator overloadingControl structureComment