Tacit programming: Difference between revisions
Line 66: | Line 66: | ||
|<source lang=apl>⍺ (g h) ⍵</source>|| {{←→}} ||<source lang=apl>⍺ g (h ⍵)</source> | |<source lang=apl>⍺ (g h) ⍵</source>|| {{←→}} ||<source lang=apl>⍺ g (h ⍵)</source> | ||
|} | |} | ||
=== 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 == |
Revision as of 12:17, 18 February 2022
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. Below, ⍺
and ⍵
refer to the arguments of the train. f
, g
, and h
are functions (which themselves can be tacit or not), and A
is an array. The arguments are processed by the following rules:
3-trains
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:
(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 ⍵) |
2-trains
Most dialects define a 2-train is an atop, equivalent to the function derived using the Atop operator. The left function is applied monadically on the result of the right function:
(g h) ⍵ |
g ( h ⍵) | |
⍺ (g h) ⍵ |
g (⍺ h ⍵) |
Only dzaima/APL allows (A h)
, which it treats as A∘h
.[1] See Bind.
J instead defines the 2-train as a hook, equivalent to the function derived using the Withe operator. The left function is always applied 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:
(g h) ⍵ |
⍵ g (h ⍵) | |
⍺ (g h) ⍵ |
⍺ g (h ⍵) |
Problems caused by function-operator overloading
Trains that use a 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 fork f/h
is actually parsed as the atop (f/)h
and the attempted atop f/
is actually parsed as a Windowed Reduction. There are multiple ways to mitigate this issue. For example, the fork can be enforced using the Atop operator by applying identity to the hybrid's result as f⊢⍤/h
and the atop can be enforced by using the explicit Atop operator instead of a 2-train; f⍤/
.
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, 1 0 1/⌽
and /,⊃
are parsed as forks.
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↓⍳)
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 15 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
See also
External links
Tutorials
- Learning APL: Trainspotting
- Dyalog documentation: version 14.0 release notes
- Dfns workspace: Translation of [dfns] into tacit form
- APL Cultivation: Transcribing to and reading trains
- APLtrainer: How to read trains in Dyalog APL code (video)
- APLtrainer: Function trains in APL (video)
- Dyalog webinar: Train Spotting in Dyalog APL (video)
- Dyalog '13: Train Spotting in Version 14.0 (video)
Documentation
References
- ↑ dzaima/APL: Differences from Dyalog APL. Retrieved 09 Jan 2020.