In [1]:
using APL

Basic expressions

In [2]:
apl"(ι5) ×∘ ι5"
Out[2]:
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
In [3]:
f = apl"{(ιω) ×∘ ιω}"
Out[3]:
{((ι ω) (×∘) (ι ω))}

Make in APL, use in Julia

In [4]:
f(5)
Out[4]:
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
In [5]:
apl"+/"(1:10)
Out[5]:
1-element Array{Int64,1}:
 55

Below is a reproductoin of Alan's APL notebook

In [6]:
uniq = apl"{((ιρω) = (ω ι ω))]ω}" # ] is the getindex operator
Out[6]:
{(((ι (ρ ω)) = (ω ι ω)) ] ω)}
In [7]:
v = [3, 2, 1, 3, 4, 2, 1, 7, 4, 2, 2, 3]
x = uniq(v)
Out[7]:
5-element Array{Int64,1}:
 3
 2
 1
 4
 7
In [8]:
# Frequency distribution
[x apl"{+/(α=∘ω)}"(x,v)]
Out[8]:
5x2 Array{Int64,2}:
 3  3
 2  4
 1  2
 4  2
 7  1
In [9]:
apl"16 16 16 ⊤ 877 123 43"
Out[9]:
3x3 Array{Int64,2}:
  3   0   0
  6   7   2
 13  11  11
In [10]:
apl"⊤"([1760,3,12],95)  # 95 inches = 2 yards, 1 foot, 11 inches
Out[10]:
3-element Array{Int64,1}:
  2
  1
 11
In [11]:
P=[2, 3, 7] # List of primes
E=[2, 1, 2] # List of exponents
Out[11]:
3-element Array{Int64,1}:
 2
 1
 2
In [12]:
#prod(P.^⊤(E+1 ,R ),1)

apl"{×⌿α*(ω+1)⊤(⁻1+ι×/ω+1)}"(P, E)
Out[12]:
1x18 Array{Int64,2}:
 1  7  49  3  21  147  2  14  98  6  42  294  4  28  196  12  84  588
In [13]:
apl"6 5 ρ ⍋⍋,(ι6)+∘ι5"
Out[13]:
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
In [14]:
# 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]
Out[14]:
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
In [15]:
x =apl"{ι¨ρω]ω}"(M)
Out[15]:
2-element Array{UnitRange{Int64},1}:
 1:6
 1:4
In [16]:
apl"{ (ω=(1]α) +∘ (2]α)) ] ω}"(x,M)
Out[16]:
2-element Array{Int64,1}:
 6
 9
In [17]:
apl"{ω⍉1 2}"(M),apl"{ω⍉2 1}"(M)
Out[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,

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)

This is why I love Julia...

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:

In [27]:
@code_llvm apl"-"(1, 2)
define i64 @julia_call_22549(i64, i64) {
top:
  %2 = sub i64 %0, %1
  ret i64 %2
}
In [28]:
@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

In [29]:
@code_llvm 1-2
define i64 @julia_-_22576(i64, i64) {
top:
  %2 = sub i64 %0, %1
  ret i64 %2
}
In [30]:
@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
}
In [31]:
@code_llvm apl"-⍨"(1,2)
define i64 @julia_call_22583(i64, i64) {
top:
  %2 = sub i64 %1, %0
  ret i64 %2
}
In [32]:
@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
}
In [33]:
@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

In [34]:
apl"+/⍬",apl"×/⍬", apl"×/⍬", apl"⌈/⍬", apl"⌊/⍬", apl",/⍬", apl"⌈/⍬", apl"∧/⍬", apl"∨/⍬", apl"+/"(Bool[])
Out[34]:
([0.0],[1.0],[1.0],[-Inf],[Inf],[Any[]],[-Inf],[1.0],[0.0],[0])
In [35]:
apl"+⍨/⍬",apl"×⍨/⍬", apl"×⍨/⍬", apl"⌈⍨/⍬", apl"⌊⍨/⍬", apl",⍨/⍬", apl"⌈⍨/⍬", apl"∧⍨/⍬", apl"∨⍨/⍬", apl"+⍨/"(Bool[])
Out[35]:
([0.0],[1.0],[1.0],[-Inf],[Inf],[Any[]],[-Inf],[1.0],[0.0],[0])