APL inspired arrays for Haskell
Orthotope
Lennart Augustsson
(Epic Games)
What’s in a name?
From Wikipedia:
In geometry, an orthotope[2] (also called a hyperrectangle or a box) is the generalization of a rectangle to higher dimensions. It is formally defined as the Cartesian product of orthogonal intervals.
Since all the good names like array and tensor were taken I picked orthotope.
APL history
Orthotope
Array notation
Displayed array value | Shape | Rank | Size |
1 2 3�4 5 6 | [2,3] | 2 | 6 |
1 2�3 4�5 6 | [3,2] | 2 | 6 |
1 2 3 | [3] | 1 | 3 |
1 2 3 | [1,3] | 2 | 3 |
1�2�3 | [3,1] | 2 | 3 |
1 2 3�4 5 6��7 8 9�0 1 2 | [2,2,3] | 3 | 12 |
Array notation
Displayed array value | Shape | Rank | Size |
1 2 3��4 5 6���7 8 9��0 1 2 | [2,2,1,3] | 4 | 12 |
1 | [1] | 1 | 1 |
1 | [] | 0 | 1 |
Rank 0 arrays are allowed, often called scalars.
Array type
Array [2,3] Int -- 2x3 array of Int
Array [6] Float -- 6 element array (i.e. vector) of Int
Array [4,4,4,4] Bool — 4x4x4x4 array of Bool
Some examples: fromList, shape, rank, size, iota
> a = fromList [1..6] :: Array [2,3] Int
> pp a -- pp pretty prints
1 2 3
4 5 6
> :t fromList
fromList :: Shape sh => [a] -> Array sh a
> a = fromList @[2,3] [1..6]
> pp a
1 2 3
4 5 6
> shape a
[2,3]
> rank a
2
> size a
6
> pp $ iota @10
0 1 2 3 4 5 6 7 8 9
Some examples: index, unScalar
> pp a
1 2 3
4 5 6
> pp $ index a 1
4 5 6
> pp $ a `index` 1 `index` 0
4
> :t index
index :: KnownNat s => Array (s : sh) a -> Int -> Array sh a
> print $ a `index` 1 `index` 0
fromList @[] [4]
> print $ unScalar $ a `index` 1 `index` 0
4
> :t unScalar
unScalar :: Array '[] a -> a
Some examples: transpose
> pp a
1 2 3
4 5 6
> pp $ transpose @[1,0] a
1 4
2 5
3 6
> transpose @[2,0] a
<interactive>:27:1: error:
• Could not deduce (Elem 2 '[]) arising from a use of ‘transpose’
from the context: (Num a, Enum a)
bound by the inferred type of
it :: (Num a, Enum a) => Array (Permute '[2, 0] '[2, 3]) a
at <interactive>:27:1-18
• In the expression: transpose @[2, 0] a
In an equation for ‘it’: it = transpose @[2, 0] a
Some examples: transpose
> b = fromList @[2,3,4] [1..24]
> pp b
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
21 22 23 24
> pp $ transpose @[2,1,0] b
1 13
5 17
9 21
2 14
6 18
10 22
3 15
7 19
11 23
4 16
8 20
12 24
Some examples: transpose
> :t transpose
transpose
:: (Permutation is, Shape sh, Shape is,
KnownNat (Rank sh), Rank is <= Rank sh) =>
Array sh a -> Array (Permute is sh) a
Some examples: reshape
> pp $ reshape @[3,2] a
1 2
3 4
5 6
> pp $ reshape @[3,3,3] iota
0 1 2
3 4 5
6 7 8
9 10 11
12 13 14
15 16 17
18 19 20
21 22 23
24 25 26
> :t reshape
reshape :: (Shape sh, Shape sh', Size sh ~ Size sh') =>
Array sh a -> Array sh' a
Some examples: broadcast
> b = reshape @[2,3] $ transpose @[1,0] a
> pp b
1 3 5
2 4 6
> :t a
Array [3,2] Integer
> pp $ (broadcast @[0,2] a :: Array [3,4,2] Integer)
1 2
1 2
1 2
1 2
3 4
3 4
3 4
3 4
5 6
5 6
5 6
5 6
Some examples: arithmetic
> pp $ a + a
2 4 6
8 10 12
> pp $ a + 1
2 3 4
5 6 7
> pp $ sqrt a
1.0 1.4142135623730951 1.7320508075688772
2.0 2.23606797749979 2.449489742783178
> c = fromList @'[2] [100,1000]
> pp c
100 1000
> pp $ a + broadcast @'[0] c
101 102 103
1004 1005 1006
Some examples: reduce
> pp a
1 2 3
4 5 6
> pp $ reduce (+) 0 a
21
> :t reduce
reduce :: Shape sh =>
(a -> a -> a) -> a -> Array sh a -> Array '[] a
> sumA = reduce (+) 0
> :t sumA
sumA :: (Shape sh, Num a) => Array sh a -> Array '[] a
> pp $ sumA a
21
Some examples: rerank
> pp a
1 2 3
4 5 6
> pp $ rerank @1 sumA a
6 15
> pp $ rerank @1 sumA $ transpose @[1,0] a
5 7 9
> pp b
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
21 22 23 24
> pp $ rerank @2 sumA b
10 26 42
58 74 90
Matrix multiply
1 * 11 + 2 * 13 + 3 * 15 = 82
1 * 12 + 2 * 14 + 3 * 16 = 88
4 * 11 + 5 * 13 + 6 * 15 = 199
4 * 12 + 5 * 14 + 6 * 16 = 214
1 | 2 | 3 |
4 | 5 | 5 |
11 | 12 |
13 | 14 |
15 | 16 |
*
=
82 | 88 |
199 | 214 |
Some examples: matrix multiply
> pp a
1 2 3
4 5 6
> b = fromList @[3,2] [11..16]
> pp b
11 12
13 14
15 16
> pp $ transpose @[1,0] b
11 13 15
12 14 16
Some examples: matrix multiply
> as = broadcast @[0,2] a :: Array [2,2,3] Int
> pp as
1 2 3
1 2 3
4 5 6
4 5 6
> bs = broadcast @[1,2] (transpose @[1,0] b) :: Array [2,2,3] Int
> pp bs
11 13 15
12 14 16
11 13 15
12 14 16
> cs = as * bs
> pp cs
11 26 45
12 28 48
44 65 90
48 70 96
> ds = rerank @2 sumA cs
> pp ds
82 88
199 214
Some examples: matrix multiply
matMul :: (KnownNat n, KnownNat o, KnownNat m, Num a) =>
Array [m,n] a -> Array [n,o] a-> Array [m,o] a
matMul a b =
let as = broadcast @[0,2] a
bs = broadcast @[1,2] $ transpose @[1,0] b
in rerank @2 sumA (as * bs)
sumA :: (Shape sh, Num a) =>
Array sh a -> Array '[] a
sumA = reduce (+) 0
Some examples: 1-D convolution
Vector
Kernel
Subvectors
Mul by kern
Sum
1 | 2 | 3 | 4 | 5 | 6 |
10 | 11 | 12 |
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
10 | 22 | 36 |
20 | 33 | 48 |
30 | 44 | 60 |
40 | 55 | 72 |
68 | 101 | 134 | 167 |
Some examples: 1-D convolution
> pp a
1 2 3
4 5 6
> pp k
10 11 2
> pp (window @'[3] a)
1 2 3
2 3 4
3 4 5
4 5 6
> pp (broadcast @'[1] k :: Array [4,3] Int)
10 11 12
10 11 12
10 11 12
10 11 12
> pp (window @'[3] a * broadcast @'[1] k)
10 22 36
20 33 48
30 44 60
40 55 72
> pp $ rerank @1 sumA (window @'[3] a * broadcast @'[1] k)
68 101 134 167
Some examples: 1-D convolution
conv1D :: forall sk sa a .
(KnownNat sk, KnownNat sa,
sk <= sa, Num a) =>
Array '[sk] a -> Array '[sa] a -> Array '[sa+1-sk] a
conv1D k a =
rerank @1 asum (window @'[sk] a * broadcast @'[1] k)
Complexity
Variations, typing
Variations, storage
Implementation
Array
Sizes
Steps
Offset
Vector
d1
d2
d3
.
.
.
s1
s2
s3
.
.
.
o
Implementation, example
Array
Sizes
Steps
Offset
Vector
2
3
3
1
0
1 | 2 | 3 | 4 | 5 | 6 |
Implementation, example
Array
Sizes
Steps
Offset
Vector
3
4
0
0
0
7 |
Implementation, example, transpose
Transpose
Sizes
Steps
Offset
Vector
3
2
1
3
1
1 | 2 | 3 | 4 | 5 | 6 |
Array
Sizes
Steps
Offset
Vector
2
3
3
1
0
1 | 2 | 3 | 4 | 5 | 6 |
Implementation, example, slice
Slice
Sizes
Steps
Offset
Vector
2
2
3
1
1
1 | 2 | 3 | 4 | 5 | 6 |
Array
Sizes
Steps
Offset
Vector
2
3
3
1
0
1 | 2 | 3 | 4 | 5 | 6 |
Implementation, drawbacks
Main problem: loss of locality.
Complexity again
Conclusion