using APL
apl"(ι5) ×∘ ι5"
5x5 Array{Int64,2}: 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
f = apl"{(ιω) ×∘ ιω}"
{((ι ω) (×∘) (ι ω))}
f(5)
5x5 Array{Int64,2}: 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
apl"+/"(1:10)
1-element Array{Int64,1}: 55
Below is a reproductoin of Alan's APL notebook
uniq = apl"{((ιρω) = (ω ι ω))]ω}" # ] is the getindex operator
{(((ι (ρ ω)) = (ω ι ω)) ] ω)}
v = [3, 2, 1, 3, 4, 2, 1, 7, 4, 2, 2, 3]
x = uniq(v)
5-element Array{Int64,1}: 3 2 1 4 7
# Frequency distribution
[x apl"{+/(α=∘ω)}"(x,v)]
5x2 Array{Int64,2}: 3 3 2 4 1 2 4 2 7 1
apl"16 16 16 ⊤ 877 123 43"
3x3 Array{Int64,2}: 3 0 0 6 7 2 13 11 11
apl"⊤"([1760,3,12],95) # 95 inches = 2 yards, 1 foot, 11 inches
3-element Array{Int64,1}: 2 1 11
P=[2, 3, 7] # List of primes
E=[2, 1, 2] # List of exponents
3-element Array{Int64,1}: 2 1 2
#prod(P.^⊤(E+1 ,R ),1)
apl"{×⌿α*(ω+1)⊤(⁻1+ι×/ω+1)}"(P, E)
1x18 Array{Int64,2}: 1 7 49 3 21 147 2 14 98 6 42 294 4 28 196 12 84 588
apl"6 5 ρ ⍋⍋,(ι6)+∘ι5"
6x5 Array{Int64,2}: 1 3 6 10 15 2 5 9 14 20 4 8 13 19 24 7 12 18 23 27 11 17 22 26 29 16 21 25 28 30
# Dijkstra's Question
M=[3 16 10 11;5 1 14 14;19 8 6 17;1 2 11 14; 1 8 2 9; 14 12 19 17]
6x4 Array{Int64,2}: 3 16 10 11 5 1 14 14 19 8 6 17 1 2 11 14 1 8 2 9 14 12 19 17
x =apl"{ι¨ρω]ω}"(M)
2-element Array{UnitRange{Int64},1}: 1:6 1:4
apl"{ (ω=(1]α) +∘ (2]α)) ] ω}"(x,M)
2-element Array{Int64,1}: 6 9
apl"{ω⍉1 2}"(M),apl"{ω⍉2 1}"(M)
( 6x4 Array{Int64,2}: 3 16 10 11 5 1 14 14 19 8 6 17 1 2 11 14 1 8 2 9 14 12 19 17, 4x6 Array{Int64,2}: 3 5 19 1 1 14 16 1 8 2 8 12 10 14 6 11 2 19 11 14 17 14 9 17)
Here's the implementation explained in brief
The apl""
string macro parses and evals an APL expression. The parser works on the reverse of the string, and consists of a bunch of concatenation rules defined as a generic cons
function.
APL strings are parsed and executed to produce either a result or an object representing the APL expression. Primitve functions are of the type PrimFn{c}
where c
is the character, similarly operators are of type Op1{c, F}
and Op2{c, F1, F2}
where F
, F1
and F2
are types of the operand functions - these type parameters let you specialize how these objects are handled all over the place. an optimization is simply a method defined on an expression of a specific type
The call
generic function can be used to make these objects callable! The eval-apply is really simple and quite elegant.
Here's a look at what the JIT produces:
@code_llvm apl"-"(1, 2)
define i64 @julia_call_22549(i64, i64) { top: %2 = sub i64 %0, %1 ret i64 %2 }
@code_llvm apl"-"(1, 2.0)
define double @julia_call_22573(i64, double) { top: %2 = sitofp i64 %0 to double %3 = fsub double %2, %1 ret double %3 }
Which is the exact same as
@code_llvm 1-2
define i64 @julia_-_22576(i64, i64) { top: %2 = sub i64 %0, %1 ret i64 %2 }
@code_llvm 1-2.0
define double @julia_-_22577(i64, double) { top: %2 = sitofp i64 %0 to double %3 = fsub double %2, %1 ret double %3 }
@code_llvm apl"-⍨"(1,2)
define i64 @julia_call_22583(i64, i64) { top: %2 = sub i64 %1, %0 ret i64 %2 }
@code_llvm apl"-⍨⍨⍨⍨⍨⍨"(1,2) # Up to 6 flips get inlined!
define i64 @julia_call_22596(i64, i64) { top: %2 = sub i64 %0, %1 ret i64 %2 }
@code_llvm apl"-¨"([1,2,3]) # some things won't be inlined...
define %jl_value_t* @julia_call_22607(%jl_value_t*) { top: %1 = call %jl_value_t* @julia_map_22608(%jl_value_t* %0) ret %jl_value_t* %1 }
Identity elements for dyadic functions and element type combinations are defined here
They continue to work when an operator modifes the function
apl"+/⍬",apl"×/⍬", apl"×/⍬", apl"⌈/⍬", apl"⌊/⍬", apl",/⍬", apl"⌈/⍬", apl"∧/⍬", apl"∨/⍬", apl"+/"(Bool[])
([0.0],[1.0],[1.0],[-Inf],[Inf],[Any[]],[-Inf],[1.0],[0.0],[0])
apl"+⍨/⍬",apl"×⍨/⍬", apl"×⍨/⍬", apl"⌈⍨/⍬", apl"⌊⍨/⍬", apl",⍨/⍬", apl"⌈⍨/⍬", apl"∧⍨/⍬", apl"∨⍨/⍬", apl"+⍨/"(Bool[])
([0.0],[1.0],[1.0],[-Inf],[Inf],[Any[]],[-Inf],[1.0],[0.0],[0])