Apple by Example

Vanessa McHale

Introduction

Apple is an experimental compiler with a working though not-perfectly-designed typed frontend.

Installation

The REPL and typechecker are available on the release page.

Reference Card

In the REPL, type \l.

 > \l
Λ             scan                     √             sqrt
⋉             max                      ⋊             min
⍳             integer range            ⌊             floor
ℯ             exp                      ⨳ {m,n}       convolve
\~            successive application   \`n           dyadic infix
_.            log                      'n            map
`             zip                      `{i,j∘[k,l]}  rank
𝒻             range (real)             𝜋             pi

⋮

Use :ty for more:

 > :ty (⊲)
a → Vec i a → Vec (i + 1) a

:help inside the REPL:

 > :help
:help, :h                    Show this help
:ty            <expression>  Display the type of an expression
:ann           <expression>  Annotate with types
:bench, :b     <expression>  Benchmark an expression
:list                        List all names that are in scope
⋮

Editor Integration

There is a vim plugin and a VSCode extension.

The file extension is .🍎 or .🍏.

Capabilities

Integer Range

To generate an integer range use irange or (APL iota).

 > ⍳
⍳ : int → int → int → Vec #n int

takes a start value, end value, and step size as arguments, viz.

 > ⍳ 0 9 1
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 > irange 30 0 _3
[30, 27, 24, 21, 18, 15, 12, 9, 6, 3, 0]

Note that _ is used for negative literals.

Real Range

For a range of real numbers, use frange or 𝒻.

 > :ty frange
float → float → int → Vec #n float

frange takes a start value, an end value, and the number of steps.

 > frange 0 9 10
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

Map

' maps over an array.

 > (*2)'⍳ 0 9 1
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Functions can be curried.

Zip

Pick the greater value among two vectors:

 > (⋉)`⟨0,_1,3.0⟩ ⟨_3,1,3⟩
Vec 3 [0.0, 1.0, 3.0]

Fold

/ folds over an array.

 > (+)/⍳ 1 100 1
5050

Scan

 > (+)Λ (irange 1 3 1)
Vec 3 [1, 3, 6]

Array Literals

Array literals are delineated by .

 > ⟨_1,0::int⟩
[-1, 0]
 > ⟨⟨0,1⟩,⟨_1,0::int⟩⟩
[ [0, 1]
, [-1, 0] ]

Reverse

~ reverses an array.

 > ~(irange 0 9 1)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Reverse applied to a higher-dimensional array reverses elements (sub-arrays) along the first dimension.

 > ⟨⟨0,1⟩,⟨1,0::int⟩,⟨2,4⟩⟩
[ [0, 1]
, [1, 0]
, [2, 4] ]
 > ~⟨⟨0,1⟩,⟨1,0::int⟩,⟨2,4⟩⟩
[ [2, 4]
, [1, 0]
, [0, 1] ]
 > ~'⟨⟨0,1⟩,⟨1,0::int⟩,⟨2,4⟩⟩
[ [1, 0]
, [0, 1]
, [4, 2] ]

Outer Product

The outer product creates a table by applying some function.

 > :ty \f.\x.\y. x f⊗ y
(a → b → c) → Arr sh0 a → Arr sh1 b → Arr (sh0 ⧺ sh1) c
 > (frange 0 9 10) (*)⊗ (frange 0 9 10)
[ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
, [0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0]
, [0.0, 3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0, 24.0, 27.0]
, [0.0, 4.0, 8.0, 12.0, 16.0, 20.0, 24.0, 28.0, 32.0, 36.0]
, [0.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0]
, [0.0, 6.0, 12.0, 18.0, 24.0, 30.0, 36.0, 42.0, 48.0, 54.0]
, [0.0, 7.0, 14.0, 21.0, 28.0, 35.0, 42.0, 49.0, 56.0, 63.0]
, [0.0, 8.0, 16.0, 24.0, 32.0, 40.0, 48.0, 56.0, 64.0, 72.0]
, [0.0, 9.0, 18.0, 27.0, 36.0, 45.0, 54.0, 63.0, 72.0, 81.0] ]
 > (frange 0 4 5) [(x,y)]⊗ (frange 0 4 5)
[ [(0.0*0.0), (0.0*1.0), (0.0*2.0), (0.0*3.0), (0.0*4.0)]
, [(1.0*0.0), (1.0*1.0), (1.0*2.0), (1.0*3.0), (1.0*4.0)]
, [(2.0*0.0), (2.0*1.0), (2.0*2.0), (2.0*3.0), (2.0*4.0)]
, [(3.0*0.0), (3.0*1.0), (3.0*2.0), (3.0*3.0), (3.0*4.0)]
, [(4.0*0.0), (4.0*1.0), (4.0*2.0), (4.0*3.0), (4.0*4.0)] ]

Successive Application

 > :ty (\~)
(a → a → b) → Arr (i + 1 `Cons` sh) a → Arr (i `Cons` sh) b

[(-)\~ x] gives successive differences.

 > (-)\~ ((^2)'(frange 0 9 10))
[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0]

Rotate

 > (⊖)
(⊖) : int → Vec i a → Vec i a
 > 2 ⊖ irange 0 9 1
[2, 3, 4, 5, 6, 7, 8, 9, 0, 1]
 > _2 ⊖ irange 0 9 1
[8, 9, 0, 1, 2, 3, 4, 5, 6, 7]
 > 2 ⊖ ⟨⟨1,2⟩,⟨3,4⟩,⟨5,6.0⟩⟩
Arr (3×2) [ [5.0, 6.0]
          , [1.0, 2.0]
          , [3.0, 4.0] ]

Cycle

cyc. or (infix) concatenates an array with itself a specified number of times.

 > cyc. ⟨0::int,1⟩ 4
Vec 8 [0, 1, 0, 1, 0, 1, 0, 1]
 > ⟨0::int,1⟩⊙4
Vec 8 [0, 1, 0, 1, 0, 1, 0, 1]

Repeat

 > re: 3 ⟨1.0,0⟩
Arr (3×2) [ [1.0, 0.0]
          , [1.0, 0.0]
          , [1.0, 0.0] ]

Transpose

⍉ or |:

 > ⍉ ⟨⟨1.0,3⟩,⟨4,4⟩,⟨2,_2⟩⟩
[ [1.0, 4.0, 2.0]
, [3.0, 4.0, -2.0] ]
 > ⟨⟨1.0,3⟩,⟨4,4⟩,⟨2,_2⟩⟩
[ [1.0, 3.0]
, [4.0, 4.0]
, [2.0, -2.0] ]

Unfold

 > gen. (1::int) (*2) 10
Vec 10 [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

Dyadic Infix

Moving average:

 > [((+)/x)%ℝ(:x)]\` 7 (frange 0 9 10)
[3.0, 4.0, 5.0, 6.0]
 > [x]\`4 (frange 0 5 6)
[ [0.0, 1.0, 2.0, 3.0]
, [1.0, 2.0, 3.0, 4.0]
, [2.0, 3.0, 4.0, 5.0] ]

Bind

 > {i←2::int;i*i}
4

Bind, preventing inlining:

 > {i⟜2::int;i*i}
4

One can see that 2 is stored in a register by inspecting the generated assembly:

 > :asm {i←2::int;i*i}

    mov x0, #0x4
    ret
 > :asm {i⟜2::int;i*i}

    mov x0, #0x2
    mul x0, x0, x0
    ret

Polymorphic Bind

 > {sum ⇐ [(+)/x]; sum (irange 0 9 1)+⌊(sum(frange 0 9 10))}
90
 > {sum ← [(+)/x]; sum (irange 0 9 1)+⌊(sum(frange 0 9 10))}
1:42: could not unify 'float' with 'int' in expression '𝒻 0 9 10'

Rank

Rank `{i,j∘[k,l]} lifts a function to operate on i, j-cells, optionally specifying axes k,l. Iteration is bottom-up; by contrast map ' cuts across the leading dimension.

To make a scalar function apply to arrays, re-rank

 > :ty ((*)`{0,0})
(IsNum c) :=> Arr sh c → Arr sh c → Arr sh c

Sigmoid on an arbitrary-dimension array:

([1%(1+ℯ(_x))]`{0})
 > ⟨⟨0,1,2⟩,⟨3,4,5::int⟩⟩
Arr (2×3) [ [0, 1, 2]
          , [3, 4, 5] ]
 > {sum←[(+)/x]; sum`{1} ⟨⟨0,1,2⟩,⟨3,4,5::int⟩⟩}
Vec 3 [3, 5, 7]
 > {sum←[(+)/x]; sum`{1∘[2]} ⟨⟨0,1,2⟩,⟨3,4,5::int⟩⟩}
Vec 2 [3, 12]

This may be confusing; Apple’s rank feature was poorly designed.

Take 0-cells (scalars) from the first array and 1-cells from the second.

 > (⊲)`{0,1∘[2]} ⟨0::int,1⟩ ⟨⟨2,3⟩,⟨4,5⟩⟩
Arr (2×3) [ [0, 2, 3]
          , [1, 4, 5] ]
 > ⍉ ((2 ⊖)`{1} ⟨⟨1,2⟩,⟨3,4⟩,⟨5,6.0⟩⟩)
Arr (3×2) [ [5.0, 6.0]
          , [1.0, 2.0]
          , [3.0, 4.0] ]
 > :ty [♭`{3∘[2,3,4]} (x :: Arr (60000 × 28 × 28 × 1) float)]
Arr (60000 × 28 × 28 × 1) float → Arr (60000 × 784) float

Random Numbers

𝔯 or rand.

 > 𝔯 0 1 :: float
0.582699021207219
 > 𝔯 0 1 :: Arr (3 × 3) float
Arr (3×3) [ [0.9827522631010304, 0.30678513061418045, 0.6008551364891055]
          , [0.608715653358658, 0.2124387982011875, 0.8858951305876062]
          , [0.30465710174579286, 0.15185986406856955, 0.33766190287353126] ]

Tuples

Use ->n to access the nth element of a tuple, viz.

 > (1.0,#t,4::int)->1
1.0
 > (1.0,#t,4::int)->2
#t
 > (1.0,#t,4::int)->3
4

Convolve

Convolve (⨳ {m,n}) is like dyadic infix for higher-rank windows.

 > [(⋉)/x] ⨳ {3} ⟨_1,0,4,_2,3,3,1,1,0,_5.0⟩
Vec 8 [4.0, 4.0, 4.0, 3.0, 3.0, 3.0, 1.0, 1.0]
 > [(⋉)/x] \`3 ⟨_1,0,4,_2,3,3,1,1,0,_5.0⟩
Vec 8 [4.0, 4.0, 4.0, 3.0, 3.0, 3.0, 1.0, 1.0]
 > ([((+)/* 0 (x::Arr (2 × 3) float))%ℝ(:x)] ⨳ {2,3}) ⟨⟨1.0,2,0,0⟩,⟨_1,_2,3,4⟩,⟨5,6,3,1⟩,⟨3,1,1,3⟩⟩
Arr (3×2) [ [0.5, 1.1666666666666665]
          , [2.333333333333333, 2.5]
          , [3.1666666666666665, 2.5] ]

REPL Functionality

Load

 > :yank chisqcdf math/chisqcdf.🍎
 > chisqcdf 10 28
0.9982004955179669

math/chisqcdf.🍎 is a file that contains an expression of type float → float→ float.

Benchmark

 > :bench frange 0 999 1000
benchmarking...
time                 1.423 μs   (1.417 μs .. 1.427 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 1.426 μs   (1.422 μs .. 1.429 μs)
std dev              11.94 ns   (9.819 ns .. 14.22 ns)

QuickCheck

Apple can generate shape-correct test cases for property testing. For instance,

 > :qc \x. [(+)/(*)`x y] x x >= 0.0
Passed, 100.

tests that the dot product of a vector with itself is nonnegative.

Exotic Syntax

Coronis

Instead of

{x←y;z}

One can write

⸎x←y;z

Using the typographical coronis.

Matrix Dimensions

One can specify matrix dimensions in a type signature with unicode subscript digits separated by a comma.

𝔯 0 1 :: M ₁₂,₁₂ float

is equivalent to

𝔯 0 1 :: Arr (12 × 12) float

Identity Matrix

👁️ can be used in place of eye. for the identity matrix.

Identifiers

Identifiers may be latin or greek characters, or a single character from the mathematical greek or mathematical latin unicode block, optionally followed by some subscript alphanumeric characters. The single-character lambda λ is reserved. , 𝛻, and are also identifiers but may not be followed by a subscript.

Thus pxs, aₙ, sn₁, 𝐶, φs, 𝜉, and 𝜌₀ are valid identifiers but 𝜉s is not.

Vulgar Fractions

Unicode vulgar fractions are considered float literals:

 > ⅚
0.8333333333333334

One can use for the reciprocal, viz.

 > [⅟(1+ℯ(_x))]
λx. 1.0 % (1 + 2.718281828459045 ** _ x) : float → float

Examples

Linear Regression

λxs.λys.
{
  Σ ← [(+)/x];
  n ⟜ ℝ(:xs);
  xbar ⟜ (Σ xs) % n; ybar ⟜ (Σ ys) % n;
  xy ⟜ Σ ((*)`xs ys);
  x2 ⟜ Σ ((^2)'xs);
  denom ⟜ (x2-n*(xbar^2));
  a ← ((ybar*x2)-(xbar*xy))%denom;
  b ← (xy-(n*xbar*ybar))%denom;
  (a,b)
}

Note the to prevent expressions from being inlined.

Kullback-Leibler Divergence

λp.λq. (+)/([x*_.(x%y)]`p q)

Numerically Stable Geometric Mean

λxs.
  ⸎ avg ← [{n ⟜ ℝ(:xs); ((+)/xs)%n}]
  ; e:(avg (_.'xs))

Array Strides

λds. }:((*)Λₒ 1::int ds)

(Column-major order)

Train Neural Network

λwh.λwo.λbh.λbo.
{ X ⟜ ⟨⟨0,0⟩,⟨0,1⟩,⟨1,0⟩,⟨1,1⟩⟩;
  Y ⟜ ⟨0,1,1,0⟩;
  sigmoid ← [1%(1+ℯ(_x))];
  sDdx ← [x*(1-x)];
  sum ⇐ [(+)/x];
  ho ⟜ sigmoid`{0} ([(+)`bh x]'(X%.wh));
  prediction ⟜ (sigmoid ∴ (+bo))'(ho%:wo);
  l1E ← (-)`Y prediction;
  l1Δ ⟜ (*)`(sDdx'prediction) l1E;
  he ← l1Δ (*)⊗ wo;
  hΔ ⟜ (*)`{0,0} (sDdx`{0} ho) he;
  wha ← (+)`{0,0} wh ((|:X)%.hΔ);
  woa ← (+)`wo ((|:ho)%:l1Δ);
  bha ← [(+)/ₒ x y]`{0,1} bh hΔ;
  boa ← bo + sum l1Δ;
  (wha,woa,bha,boa)
}

This is equivalent to the Python:

import numpy as np

def sigmoid (x):
    return 1/(1 + np.exp(-x))

def sigmoid_derivative(x):
    return x * (1 - x)

inputs = np.array([[0,0],[0,1],[1,0],[1,1]])
expected_output = np.array([[0],[1],[1],[0]])

hidden_layer_activation = np.dot(inputs,hidden_weights)
hidden_layer_activation += hidden_bias
hidden_layer_output = sigmoid(hidden_layer_activation)

output_layer_activation = np.dot(hidden_layer_output,output_weights)
output_layer_activation += output_bias
predicted_output = sigmoid(output_layer_activation)

#Backpropagation
error = expected_output - predicted_output
d_predicted_output = error * sigmoid_derivative(predicted_output)

error_hidden_layer = d_predicted_output.dot(output_weights.T)
d_hidden_layer = error_hidden_layer * sigmoid_derivative(hidden_layer_output)

#Updating Weights and Biases
output_weights += hidden_layer_output.T.dot(d_predicted_output)
output_bias += np.sum(d_predicted_output,axis=0,keepdims=True)
hidden_weights += inputs.T.dot(d_hidden_layer)
hidden_bias += np.sum(d_hidden_layer,axis=0,keepdims=True)

Shoelace Theorem

If a polygon has vertices at \(x_n\), \(y_n\), then its area is given by

\[A=\frac{1}{2}\Biggl|(x_1y_2+x_2y_3+\cdots+x_ny_1)-(y_1x_2+y_2x_3+\cdots+y_nx_1)\Biggr|\]

λxs.λys.
    { sum ⇐ [(+)/x]
    ; 0.5*abs.(sum((*)`xs (1⊖ys)) - sum((*)`(1⊖xs) ys))
    }

Note the array style: , ` (zip), and fold are enough to eschew pointful definitions.

Polynomials

Evaluation

λp.λx. (+)/ ((*)`(~p) (gen. 1 (*x) (𝓉p)))

Array

To take all but the last 6 elements:

[{.\`7 x]

To drop the first 6 elements:

[}.\`7 x]

Take the first 7 elements:

 > {. ([x] \`7 (irange 0 9 1))
Vec 7 [0, 1, 2, 3, 4, 5, 6]

Take the last 7 elements:

 > }. ([x] \`7 (irange 0 9 1))
Vec 7 [3, 4, 5, 6, 7, 8, 9]

Matrix-Vector Multiplication

λA.λx.
{
  dot ⇐ [(+)/((*)`x y)];
  (dot x)`{1∘[2]} (A::Arr (i × j) float)
}

Filter

\p.\xs. (xs˙)'p⩪xs

Argmax

[{m⟜(⋉)/x::Vec n float; (=m)@.x}]

Luhn Check

λxs.
  { digitSum ← [?x>10,.x-9,.x]
  ; t ← (+)/ [digitSum (x*y)]`(~(}:xs)) (}: (cyc. ⟨2,1::int⟩ 8))
  ; 10-(t|10)=}.xs
  }

Note zipping with cyc. ⟨2,1::int⟩ 8 to get alternating 2, 1, … factors.

Elliptic Fourier Series

From Kuhl and Giardnia, the coefficients are given by:

\[ a_n = \frac{T}{2n^2\pi^2}\sum_{p=1}^K \frac{\Delta x_p}{\Delta t_p}\left(\cos\frac{2n\pi t_p}{T} - \cos\frac{2n\pi t_{p-1}}{T}\right) \]

\[ b_n = \frac{T}{2n^2\pi^2}\sum_{p=1}^K \frac{\Delta x_p}{\Delta t_p}\left(\sin\frac{2n\pi t_p}{T} - \sin\frac{2n\pi t_{p-1}}{T}\right) \]

\[ c_n = \frac{T}{2n^2\pi^2}\sum_{p=1}^K \frac{\Delta y_p}{\Delta t_p}\left(\cos\frac{2n\pi t_p}{T} - \cos\frac{2n\pi t_{p-1}}{T}\right) \]

\[ d_n = \frac{T}{2n^2\pi^2}\sum_{p=1}^K \frac{\Delta y_p}{\Delta t_p}\left(\sin\frac{2n\pi t_p}{T} - \sin\frac{2n\pi t_{p-1}}{T}\right) \]

The offsets are given by:

\[ A_0 = \frac{1}{T} \sum_{p=1}^K \left[\frac{\Delta x_p}{2\Delta t_p} (t_p^2-t_{p-1}^2) + \xi_p (t_p-t_{p-1}^2)\right]\]

\[ C_0 = \frac{1}{T} \sum_{p=1}^K \left[\frac{\Delta y_p}{2\Delta t_p} (t_p^2-t_{p-1}^2) + \delta_p (t_p-t_{p-1}^2)\right]\]

where

\[ \xi_p = \sum_j^{p-1} \Delta x_j - \frac{\Delta x_p}{\Delta t_p} \sum_j^{p-1}\Delta t_j \]

\[ \delta_p = \sum_j^{p-1} \Delta y_j - \frac{\Delta y_p}{\Delta t_p} \sum_j^{p-1}\Delta t_j \]

\[ \xi_0,~\delta_0 = 0 \]

In Apple we can generate the first N coefficients alongside the offsets with:

λxs.λys.λN.
  { sum ← [(+)/x]
  ; tieSelf ← [({.x)⊳x]; Δ ← [(-)\~(tieSelf x)]
  ; dxs ⟜ Δ xs; dys ⟜ Δ ys
  ; dts ⟜ [√(x^2+y^2)]`dxs dys
  ; dxss ⟜ ((%)`dxs dts); dyss ⟜ ((%)`dys dts)
  ; pxs ← (+)Λ dxs; pys ← (+)Λ dys; pts ⟜ (+)Λₒ 0 dts; T ⟜}. pts
  ; coeffs ← λn.
    { n ⟜ ℝn; k ⟜ 2*n*𝜋%T
    ; cosDiffs ⟜ (-)\~([cos.(k*x)]'pts)
    ; sinDiffs ⟜ (-)\~([sin.(k*x)]'pts)
    ; c ⟜ T%(2*n^2*𝜋^2)
    ; aₙ ← c*sum ((*)`dxss cosDiffs)
    ; bₙ ← c*sum ((*)`dxss sinDiffs)
    ; cₙ ← c*sum ((*)`dyss cosDiffs)
    ; dₙ ← c*sum ((*)`dyss sinDiffs)
    ; (aₙ,bₙ,cₙ,dₙ)
    }
  ; dtss ⟜ (-)\~((^2)'pts)
  ; ppts ⟜ {: pts
  ; 𝜉 ← (-)`pxs ((*)`((%)`dxs dts) ppts)
  ; 𝛿 ← (-)`pys ((*)`((%)`dys dts) ppts)
  ; A ← (0.5*sum ((*)`((%)`dxs dts) dtss) + sum ((*)`𝜉 dts))%T
  ; C ← (0.5*sum ((*)`((%)`dys dts) dtss) + sum ((*)`𝛿 dts))%T
  ; (coeffs'(irange 1 N 1),A,C)
  }

Note the array style, e.g. (-)\~ to define successive differences on an array rather than definining pointfully.

Geography

Albers

Let 𝜆₀, 𝜑₀ be the coördinates of the origin, 𝜑₁, 𝜑₂ standard parallels, φs and lambdas the longitudes and latitudes, respectively.

\𝜆₀.\𝜑₀.\𝜑₁.\𝜑₂.\φs.\lambdas.
{
  𝑛 ⟜ (sin. 𝜑₁+sin.𝜑₂)%2;
  𝐶 ⟜ (cos. 𝜑₁)^2+2*𝑛*sin. 𝜑₁;
  𝜌₀ ⟜ √(𝐶-2*𝑛*sin. 𝜑₀)%𝑛;
  albers ← \𝜑.\𝜆.
    {
      𝜃 ⟜ 𝑛*(𝜆-𝜆₀);
      𝜌 ⟜ √(𝐶-2*𝑛*sin. 𝜑)%𝑛;
      (𝜌*sin. 𝜃, 𝜌₀-𝜌*cos. 𝜃)
    };
  albers`φs lambdas
}

Number Theory

Primality Check

λn.¬((∨)/ₒ #f ([(n|x)=0]'(⍳ 2 (⌊(√(ℝn))) 1)))

Radical

Compute the radical of an integer \(n\), \(\displaystyle \prod_{p|n} p\)

λn.
  { ni ⟜ ⌊(√(ℝn))
  ; isPrime ← λn.¬((∨)/ₒ #f ([(n|x)=0]'(⍳ 2 (⌊(√(ℝn))) 1)))
  ; pf ⇐ (isPrime #.)
  ; pps ⟜  pf ((λk. ((n|k)=0)) #. (⍳ 2 ni 1))
  ; ?ni^2=n
    ,.((*)/ₒ 1 (pf ((n/.)'(}:? pps))⧺pps))
    ,.((*)/ₒ 1 (pf (n ⊲ ((n/.)'pps))⧺pps))
  }

This shows the awkwardness of an array style.

Python Extension Module

Sample Normal Distribution

To generate normally distributed random values, we can use

\[ X = \sqrt{-2 \log B_1} \cos (2\pi B_2) \]

where \(B_1\), \(B_2\) are uniformly distributed \(\in [0,1)\).

Debugging randomness is fraught, so we turn to Python’s visualization libraries.

Image Processing

([((+)/* 0 (x::Arr (7 × 7) float))%ℝ(:x)] ⨳ {7,7}) applies a 7x7 mean filter to a 2-dimensional array.

We can use Pillow to apply it to an image:

Random Walks

Cliff Reiter points out that we can simulate a random walk by simply scanning an array of random values, viz.

(+)Λ (𝔯 _1 1 :: Vec 200 int)

Statistics

Apple is capable of statistical computing, via the program suggested by Ewart Shaw.

CDF for Normal Distribution

λz.
{
  erf ← λz.
    {
      ffact ← [(*)/ₒ 1 (𝒻 1 x (⌊x))];
      Σ ← λN.λa. (+)/ₒ 0 (a'(⍳ 0 N 1));
      (2%√𝜋)*Σ 30 (λn. {nf⟜ℝn; ((_1^n)*z^(2*n+1))%((ffact nf)*(2*nf+1))})
    };
  zz ⟜ z%(√2);
  0.5*(1+erf(zz))
}

CDF for Student’s t-distribution

λx.λν.
{
  gammaln ← λz. {
    zz ⟜ z-1;
    c0 ← 0.999999999999997092;
    𝛾 ← 607%128;
    coeffs ← ⟨ 57.1562356658629235
             , _59.5979603554754912
             , 14.1360979747417471
             , _0.491913816097620199
             , 0.339946499848118887e-4
             , 0.465236289270485756e-4
             , _0.983744753048795646e-4
             , 0.158088703224912494e-3
             , _0.210264441724104883e-3
             , 0.217439618115212643e-3
             , _0.164318106536763890e-3
             , 0.844182239838527433e-4
             , _0.261908384015814087e-4
             , 0.368991826595316234e-5
             ⟩;
    ss ← (+)/ ([y%(zz+itof x)]`(⍳ 1 14 1) coeffs);
    (((zz+0.5)*_.(zz+𝛾+0.5))-(zz+𝛾+0.5))+_.((√(2*𝜋))*(c0+ss))
  };
  Γ ⟜ [ℯ(gammaln x)];
  f21 ← λa0.λa1.λb.λz. {
    rf ← [(*)/ₒ 1 (𝒻 x (x+y-1) (⌊y))]; fact ← rf 1;
    Σ ← λN.λa. (+)/ₒ 0 (a'(⍳ 0 N 1));
    term ← λn. {nn⟜ℝ n; rf a0 nn*(rf a1 nn%rf b nn)*(z^n%fact nn)};
    Σ 50 term
  };
  0.5+x*Γ(0.5*(ν+1))%((√(𝜋*ν))*Γ(ν*0.5))*f21 0.5 ((ν+1)%2) 1.5 (_(x^2%ν))
}

This uses the Lanczos approximation to compute the gamma function; it is not built-in to Apple.

Note the Γ ⟜ [ℯ(gammaln x)]; this prevents the function from being inlined and thence speeds compilation.

CDF for F-distribution

λn.λm.λx.
{
  incΒ ← λz.λa.λb.
    {
      f21 ← λa0.λa1.λb.λz.
        {
          rf ← [(*)/ₒ 1 (𝒻 x (x+y-1) (⌊y))]; fact ← rf 1;
          Σ ← λN.λa. (+)/ₒ 0 (a'(⍳ 0 N 1));
          term ← λn. {nn⟜ℝ n; ((rf a0 nn)*(rf a1 nn)%(rf b nn))*((z^n)%(fact nn))};
          Σ 30 term
        };
      ((z**a)%a)*f21 a (1-b) (a+1) z
    };
  Β ← λx.λy.
    {
      gammaln ⟜ λz.
        {
          zz ⟜ z-1;
          c0 ← 0.999999999999997092;
          𝛾 ← 607%128;
          coeffs ← ⟨ 57.1562356658629235
                   , _59.5979603554754912
                   , 14.1360979747417471
                   , _0.491913816097620199
                   , 0.339946499848118887e-4
                   , 0.465236289270485756e-4
                   , _0.983744753048795646e-4
                   , 0.158088703224912494e-3
                   , _0.210264441724104883e-3
                   , 0.217439618115212643e-3
                   , _0.164318106536763890e-3
                   , 0.844182239838527433e-4
                   , _0.261908384015814087e-4
                   , 0.368991826595316234e-5
                   ⟩;
          ss ← (+)/ ([y%(zz+ℝ x)]`(⍳ 1 14 1) coeffs);
          (((zz+0.5)*_.(zz+𝛾+0.5))-(zz+𝛾+0.5))+_.((√(2*𝜋))*(c0+ss))
        };
      e:(gammaln x+gammaln y-gammaln (x+y))
    };
  I ← λz.λa.λb. incΒ z a b%Β a b;
  I ((n*x)%(m+n*x)) (n%2) (m%2)
}

Note that Apple does not have imports so we have to re-type the definition of the gamma function each time.