The Console
& the Matrix
One is for telling your Mac what to do in plain-ish English. The other is for telling mathematics what to do in vectors and matrices. This page teaches both — from first principles to working code — and proves why the numerical methods actually work.
Two languages, one page
It is unusual to learn AppleScript and GNU Octave side by side, because they answer opposite questions. AppleScript answers "how do I make my computer do a chore for me?" — open apps, rename files, click buttons, glue programs together. Octave answers "how do I compute this number?" — multiply matrices, solve equations, integrate functions, plot results.
Studying them together is a good way to feel the full spread of what "programming" means. One reads almost like a sentence; the other reads like a page of mathematics. Throughout, AppleScript is marked in amber and Octave in teal. The proofs at the end are marked in gold — they justify the numerical recipes you will have just used.
What AppleScript is
AppleScript is Apple's scripting language for automating applications. It was introduced in 1993 and sits on top of a system called the Open Scripting Architecture. The core idea: applications publish a "dictionary" of things they can do (a Finder can move files, Safari can return the URL of a tab), and your script sends those commands as Apple Events.
Its defining trait is an English-like syntax. Where Python says finder.trash(file), AppleScript says move file to trash. This makes simple scripts wonderfully readable and complicated scripts occasionally maddening, because the same idea can be phrased several ways.
- Runs on macOS only — author it in Script Editor (/Applications/Utilities) or run from the terminal with
osascript. - Best at gluing apps together: Finder, Mail, Safari, Calendar, Music, Photos, Microsoft Office, and anything "scriptable."
- Can drop down to the Unix shell with
do shell script, and can drive any app's menus/buttons through System Events (UI scripting).
Syntax & variables
Statements are usually one per line. Comments use -- for a line or (* … *) for a block. You create a variable with set … to …, and you join text with the & operator.
-- a line comment
(* a block comment:
spans many lines *)
set greeting to "Hello"
set who to "world"
set message to greeting & ", " & who & "!"
display dialog message -- pops a window
return message --> "Hello, world!"
A handful of things to internalise early:
- No semicolons. Line breaks end statements. Use
¬(Option-L) to continue a long line. - Assignment is
set x to v, notx = v. You read a value back withget(often implicit). - The result of the last expression is shown in Script Editor's result pane;
returnhands a value back from a script or handler. - Equality is
=or the wordsis equal to; inequality is≠oris not equal to.
Data types you'll actually use
AppleScript's everyday types are text, integer, real, boolean, date, list, and record. Lists are ordered (1-indexed!) collections; records are key–value bundles. Converting between types is called coercion and uses the word as.
-- LISTS are 1-indexed, ordered
set primes to {2, 3, 5, 7, 11}
set first to item 1 of primes --> 2
set last to last item of primes --> 11
set howMany to count of primes --> 5
set end of primes to 13 -- append → {2,3,5,7,11,13}
-- RECORDS are key:value (unordered)
set person to {name:"Ada", born:1815, active:true}
set n to name of person --> "Ada"
-- COERCION with `as`
set x to "42" as integer --> 42 (text → integer)
set s to 3.14 as text --> "3.14"
set joined to {"a", "b", "c"} as text -- uses text item delimiters
Splitting & joining text — the delimiter trick
AppleScript has no split() function. Instead you temporarily set text item delimiters, then coerce. It's the one idiom every AppleScripter memorises.
set csv to "red,green,blue"
-- SPLIT: set delimiter, read text items
set AppleScript's text item delimiters to ","
set parts to text items of csv --> {"red","green","blue"}
-- JOIN: set delimiter, coerce list → text
set AppleScript's text item delimiters to " | "
set joined to parts as text --> "red | green | blue"
-- ALWAYS reset delimiters afterwards (they're global!)
set AppleScript's text item delimiters to ""
Control flow
Conditionals are if … then … else … end if. Loops are all spelled repeat, with several flavours.
set score to 82
if score ≥ 90 then
set grade to "A"
else if score ≥ 80 then
set grade to "B"
else
set grade to "C or below"
end if
-- repeat a fixed number of times
repeat 3 times
beep
end repeat
-- counted loop
set total to 0
repeat with i from 1 to 100
set total to total + i
end repeat -- total = 5050
-- iterate over a list
repeat with fruit in {"apple", "pear", "plum"}
log (fruit as text) -- log → Script Editor's log pane
end repeat
-- conditional loop
set n to 10
repeat while n > 0
set n to n - 1
end repeat
Boolean operators are the words and, or, not. Strings have friendly tests too: contains, starts with, ends with.
Handlers (functions)
A reusable function is called a handler. Define it with on name(params) … end name and call it normally. Handlers are how you keep scripts tidy.
-- positional parameters
on circleArea(r)
return 3.14159265 * r * r
end circleArea
set a to circleArea(5) --> 78.539816...
-- a handler that filters a list
on evensIn(aList)
set out to {}
repeat with x in aList
if (x mod 2) = 0 then set end of out to (x as integer)
end repeat
return out
end evensIn
evensIn({1, 2, 3, 4, 5, 6}) --> {2, 4, 6}
-- labeled parameters read like English
on greet someone given loudly:isLoud
set msg to "Hi " & someone
if isLoud then set msg to msg & "!!!"
return msg
end greet
greet "Grace" given loudly:true --> "Hi Grace!!!"
The whole point: driving applications
You command an app inside a tell application "Name" … end tell block. Inside it, the app's own vocabulary becomes available.
-- Ask Finder how many items are on the desktop
tell application "Finder"
set n to count (every item of desktop)
set whereAmI to name of home
end tell
-- Get the URL of Safari's front tab
tell application "Safari"
set u to URL of current tab of front window
end tell
-- Speak text and put something on the clipboard
say "Build complete" using "Samantha"
set the clipboard to "copied by a script"
-- UI scripting: click a menu when an app isn't scriptable
tell application "System Events"
tell process "TextEdit"
click menu item "New" of menu "File" of menu bar 1
end tell
end tell
Errors & the Unix escape hatch
Wrap risky code in try … on error … end try. And when AppleScript can't do something directly, do shell script runs any command-line program and returns its output as text.
try
set x to "not a number" as integer -- this fails
on error errMsg number errNum
display dialog "Failed: " & errMsg & " (#" & errNum & ")"
end try
-- Borrow the power of the shell
set today to do shell script "date +%Y-%m-%d"
set files to do shell script "ls -1 ~/Documents | wc -l"
display dialog "On " & today & " you have " & files & " documents."
Live: build a dialog
AppleScript needs macOS, so we can't run it in a browser. But its single most common command — display dialog — produces a predictable window. Compose one below and press Run to see a faithful simulation of what macOS would show, alongside the exact AppleScript that produces it.
display dialog — simulator
simulated · macOSWhat GNU Octave is
GNU Octave is a free, open-source language for numerical computation. It is largely compatible with MATLAB, so the same scripts often run in both. Its native object is not a scalar but a matrix — everything is an array, and a plain number is just a 1×1 matrix. That single design choice makes linear algebra, statistics, and signal processing feel effortless.
- Comments use
%or#. A trailing;suppresses the automatic echo of a result. - Cross-platform (macOS, Linux, Windows). Run interactively, or as scripts with
octave file.m. - Batteries included: linear algebra, root-finding, integration, ODEs, FFTs, optimisation, plotting.
Vectors & matrices
Spaces or commas separate columns; semicolons separate rows. Ranges and linspace build vectors without typing every element.
v = [1 2 3 4]; % a row vector
c = [1; 2; 3]; % a column vector
A = [1 2; 3 4]; % a 2x2 matrix
r = 0:2:10; % 0 2 4 6 8 10 (start:step:stop)
x = linspace(0, 1, 5); % 0 0.25 0.5 0.75 1
size(A) % ans = 2 2
length(v) % ans = 4
A(2, 1) % ans = 3 (row 2, col 1 — 1-indexed!)
A(:, 1) % first column → [1; 3]
zeros(2,3), ones(2), eye(3) % zeros, ones, identity
A(i, j) is row i, column j. A lone colon : means "all of this dimension."Operations: the dot is everything
This is the concept that trips up every newcomer. * means matrix multiplication. Put a dot in front — .*, ./, .^ — and it becomes element-by-element.
A = [1 2; 3 4];
B = [5 6; 7 8];
A * B % MATRIX product → [19 22; 43 50]
A .* B % ELEMENT product → [5 12; 21 32]
v = [1 2 3];
v .^ 2 % [1 4 9] (square each element)
A' % transpose → [1 3; 2 4]
% reductions collapse a matrix to numbers
sum(v) % 6
mean(v) % 2
max(v) % 3
cumsum(v) % [1 3 6]
% logical indexing — keep elements that pass a test
w = [-2 5 -1 8 0];
w(w > 0) % [5 8] — selects positive entries
v^2 on a vector throws an error (it tries a matrix power and needs a square matrix). You almost always want v.^2. When in doubt, add the dot.Control flow & functions
Blocks close with end (or the explicit endif/endfor/endfunction). Anonymous functions with @(args) are the workhorse for passing math around.
% conditionals
t = 37.5;
if t > 38
disp("fever")
elseif t >= 37
disp("elevated")
else
disp("normal")
end
% a for-loop building a running sum
s = 0;
for k = 1:100
s = s + k;
end
printf("sum = %d\n", s); % sum = 5050
% ...but vectorise! same result, no loop:
s2 = sum(1:100); % 5050
% anonymous function (a closure)
f = @(x) x.^2 - 2;
f(3) % 7
f([1 2 3]) % [-1 2 7] (works on vectors)
% a named function
function y = hypotenuse(a, b)
y = sqrt(a.^2 + b.^2);
endfunction
hypotenuse(3, 4) % 5
Prefer vectorisation over loops where you can — it is shorter and dramatically faster, because the work happens in compiled array routines instead of the interpreter.
Linear algebra in one line
This is where Octave shines. Solving \(Ax = b\) is the single backslash operator A\b — and it picks a good algorithm for you (it does not naïvely compute an inverse).
A = [2 1; 1 3];
b = [3; 5];
x = A \ b % solve A x = b → [0.8; 1.4]
det(A) % 5
inv(A) % the inverse (rarely what you need)
rank(A) % 2
trace(A) % 5
% eigenvalues and eigenvectors
[V, D] = eig(A); % columns of V are eigenvectors,
% diagonal of D are eigenvalues
% least squares — same backslash, overdetermined system
M = [1 1; 1 2; 1 3];
y = [1; 2; 2];
coeffs = M \ y % best-fit line via least squares
inv(A)*b is slower and less accurate than A\b. The backslash factorises \(A\) (LU for square, QR for tall) and solves directly. Reach for inv only when you genuinely need the inverse matrix itself.Numerical methods
Three classics you'll meet constantly: finding a root of \(f\), integrating \(f\) over an interval, and fitting a polynomial. Each is built in, but knowing the recipe lets you trust the answer — and the proofs below show why these recipes converge.
% --- Newton's method by hand: solve x^2 - 2 = 0 (find sqrt 2) ---
f = @(x) x.^2 - 2;
df = @(x) 2*x;
x = 1; % initial guess
for k = 1:6
x = x - f(x) / df(x); % the Newton step
end
x % 1.41421356237 (≈ sqrt 2)
% --- numerical integration of sin from 0 to pi (true value = 2) ---
g = @(x) sin(x);
I = quad(g, 0, pi) % 2.0000 (adaptive quadrature)
% trapezoidal rule by hand on sampled data
xx = linspace(0, pi, 1000);
It = trapz(xx, sin(xx)) % ≈ 2.0000
% --- polynomial fit: degree-2 through noisy points ---
px = [0 1 2 3 4];
py = [1 3 7 13 21];
p = polyfit(px, py, 2) % coefficients of best-fit parabola
polyval(p, 5) % predict at x = 5
Live sandboxes
These four widgets re-implement Octave's behaviour in JavaScript so they run in your browser right now. The algorithms are exactly those above — only the engine differs.
Matrix workbench
A op BType matrices in Octave syntax — spaces between columns, ; between rows. Try [1 2; 3 4].
Newton's method root-finder
f(x)=0Enter f(x) using JS-style math (x*x-2, cos(x)-x, exp(x)-3). The derivative is taken numerically; watch the error square each step.
Numerical integrator
∫ trapezoid · SimpsonApproximate \(\int_a^b f(x)\,dx\) with the trapezoidal and Simpson rules and compare them.
Function plotter
plot(x, f(x))Octave's plot(x, y), in miniature. Enter a function and a domain.
The sum the loops computed
Both the AppleScript counted loop and the Octave for loop summed \(1\) through \(100\) and got 5050. That is no accident — there is a closed form, and we prove it by mathematical induction.
For every integer \(n \ge 1\), \(\displaystyle\sum_{k=1}^{n} k = \frac{n(n+1)}{2}.\)
Proof (induction on \(n\)). Let \(P(n)\) be the statement \(\sum_{k=1}^{n} k = \frac{n(n+1)}{2}\).
Base case. For \(n=1\): the left side is \(1\), and the right side is \(\frac{1\cdot 2}{2}=1\). So \(P(1)\) holds.
Inductive step. Assume \(P(n)\) holds for some \(n\ge 1\); this is the inductive hypothesis. We show \(P(n+1)\):
$$\sum_{k=1}^{n+1} k \;=\; \left(\sum_{k=1}^{n} k\right) + (n+1) \;\overset{\text{IH}}{=}\; \frac{n(n+1)}{2} + (n+1).$$Factor out \((n+1)\):
$$\frac{n(n+1)}{2} + (n+1) = (n+1)\!\left(\frac{n}{2}+1\right) = (n+1)\cdot\frac{n+2}{2} = \frac{(n+1)\big((n+1)+1\big)}{2}.$$This is exactly \(P(n+1)\). By the principle of induction, \(P(n)\) holds for all \(n\ge 1\). In particular \(\sum_{k=1}^{100} k = \frac{100\cdot 101}{2} = 5050.\) ∎
Why A*(B*C) equals (A*B)*C
Octave's matrix product is associative — you can drop the parentheses. That convenience is a theorem about sums, and the proof is a clean exercise in swapping the order of summation.
Let \(A\in\mathbb{R}^{m\times n}\), \(B\in\mathbb{R}^{n\times p}\), \(C\in\mathbb{R}^{p\times q}\). Then \((AB)C = A(BC)\).
Proof. Two matrices are equal iff every entry agrees, so we compute the \((i,\ell)\) entry of each side. Recall the definition of the product: \((XY)_{i\ell} = \sum_{r} X_{ir}Y_{r\ell}.\)
Start with the left side. Writing \(D = AB\) so that \(D_{ik} = \sum_{j=1}^{n} A_{ij}B_{jk}\),
$$\big((AB)C\big)_{i\ell} = \sum_{k=1}^{p} D_{ik}\,C_{k\ell} = \sum_{k=1}^{p}\left(\sum_{j=1}^{n} A_{ij}B_{jk}\right) C_{k\ell} = \sum_{k=1}^{p}\sum_{j=1}^{n} A_{ij}B_{jk}C_{k\ell}.$$Each term \(A_{ij}B_{jk}C_{k\ell}\) is an ordinary real number, and real addition and multiplication are associative and commutative, so the finite double sum may be reordered. Exchange the order of summation:
$$\sum_{k=1}^{p}\sum_{j=1}^{n} A_{ij}B_{jk}C_{k\ell} = \sum_{j=1}^{n}\sum_{k=1}^{p} A_{ij}B_{jk}C_{k\ell} = \sum_{j=1}^{n} A_{ij}\left(\sum_{k=1}^{p} B_{jk}C_{k\ell}\right).$$The inner sum is exactly \((BC)_{j\ell}\). Therefore
$$\sum_{j=1}^{n} A_{ij}\,(BC)_{j\ell} = \big(A(BC)\big)_{i\ell}.$$Since the \((i,\ell)\) entries agree for all \(i,\ell\), we conclude \((AB)C = A(BC)\). ∎
Newton's method, quadratically
In the live solver you watched the error roughly square at each step — \(10^{-2}\) became \(10^{-4}\), then \(10^{-8}\). That doubling of correct digits is quadratic convergence, and here is why it happens.
Let \(f\) be twice continuously differentiable near a root \(r\) with \(f(r)=0\) and \(f'(r)\neq 0\). Then for a starting point sufficiently close to \(r\), the Newton iterates \(x_{k+1}=x_k-\dfrac{f(x_k)}{f'(x_k)}\) satisfy, with \(e_k := x_k - r\), $$|e_{k+1}| \le M\,|e_k|^2 \qquad\text{where}\qquad M = \frac{\max|f''|}{2\min|f'|}.$$
Proof. Expand \(f\) about \(x_k\) using Taylor's theorem with the Lagrange remainder. Since \(f(r)=0\), there is a point \(\xi_k\) between \(x_k\) and \(r\) such that
$$0 = f(r) = f(x_k) + f'(x_k)(r-x_k) + \tfrac{1}{2}f''(\xi_k)(r-x_k)^2.$$Writing \(e_k = x_k - r\) (so \(r - x_k = -e_k\)) this reads
$$0 = f(x_k) - f'(x_k)\,e_k + \tfrac{1}{2}f''(\xi_k)\,e_k^2.$$Divide through by \(f'(x_k)\) (nonzero near the root) and solve for the combination that appears in the Newton step:
$$\frac{f(x_k)}{f'(x_k)} - e_k = -\frac{f''(\xi_k)}{2 f'(x_k)}\,e_k^2.$$Now examine the new error. By the definition of the iteration,
$$e_{k+1} = x_{k+1} - r = \Big(x_k - \frac{f(x_k)}{f'(x_k)}\Big) - r = e_k - \frac{f(x_k)}{f'(x_k)}.$$This is precisely the negative of the left-hand side above, so
$$e_{k+1} = \frac{f''(\xi_k)}{2 f'(x_k)}\,e_k^2.$$Take absolute values. On a small closed interval around \(r\) the continuous functions are bounded: \(|f''|\le \max|f''|\) and \(|f'|\ge \min|f'| > 0\). Hence
$$|e_{k+1}| \le \frac{\max|f''|}{2\,\min|f'|}\,|e_k|^2 = M\,|e_k|^2.$$If the first guess is close enough that \(M|e_0| < 1\), then \(|e_k|\to 0\), and the exponent \(2\) on \(|e_k|\) is exactly what makes the number of correct digits roughly double each step. ∎
The trapezoid's error
The integrator reported that doubling the panel count cut the error by about four. That is the signature of an \(O(h^2)\) method. We prove the error bound on a single panel; summing over panels gives the global result.
Let \(f\in C^2[a,b]\) and \(h=b-a\). The trapezoidal estimate \(T=\frac{h}{2}\big(f(a)+f(b)\big)\) satisfies, for some \(\eta\in(a,b)\), $$\int_a^b f(x)\,dx - T = -\frac{h^3}{12}\,f''(\eta).$$
Proof (sketch with the key step in full). Define the error as a function of the panel half-context by letting, for \(t\in[0,h]\),
$$E(t) = \int_{a}^{a+t} f(x)\,dx \;-\; \frac{t}{2}\big(f(a)+f(a+t)\big).$$Then \(E(0)=0\) and the quantity we want is \(E(h)\). Differentiate using the fundamental theorem of calculus and the product rule:
$$E'(t) = f(a+t) - \tfrac{1}{2}\big(f(a)+f(a+t)\big) - \tfrac{t}{2}f'(a+t) = \tfrac{1}{2}\big(f(a+t)-f(a)\big) - \tfrac{t}{2}f'(a+t).$$Differentiate once more; the \(\tfrac12 f(a)\) term is constant and drops out:
$$E''(t) = \tfrac{1}{2}f'(a+t) - \tfrac{1}{2}f'(a+t) - \tfrac{t}{2}f''(a+t) = -\frac{t}{2}\,f''(a+t).$$Now integrate back twice from \(0\) to \(h\), using \(E(0)=E'(0)=0\). Apply the weighted mean value theorem for integrals: because the weight \(-\tfrac{t}{2}\) does not change sign on \([0,h]\), there exists \(\eta\in(a,b)\) with
$$E(h) = \int_0^h\!\!\int_0^s \Big(-\frac{t}{2}f''(a+t)\Big)dt\,ds = f''(\eta)\int_0^h\!\!\int_0^s\Big(-\frac{t}{2}\Big)dt\,ds.$$The remaining integral is elementary: \(\int_0^s -\tfrac{t}{2}\,dt = -\tfrac{s^2}{4}\), and \(\int_0^h -\tfrac{s^2}{4}\,ds = -\tfrac{h^3}{12}.\) Therefore
$$\int_a^b f(x)\,dx - T = E(h) = -\frac{h^3}{12}\,f''(\eta).$$Splitting \([a,b]\) into \(n\) equal panels of width \(h=(b-a)/n\) and summing gives a total error of size \(-\frac{(b-a)h^2}{12}f''(\bar\eta)=O(h^2)\): halving \(h\) (doubling \(n\)) divides the error by \(4\), exactly as the sandbox showed. ∎
Side-by-side cheat sheet
AppleScript
- comment —
-- …/(* … *) - assign —
set x to 5 - concat —
a & b - list (1-indexed) —
{1,2,3} - record —
{name:"A", age:9} - if —
if c then … end if - loop —
repeat with i from 1 to n - function —
on f(x) … end f - app —
tell application "Finder" - errors —
try … on error … end try - shell —
do shell script "…"
GNU Octave
- comment —
% …/# … - assign (silent) —
x = 5; - matrix —
[1 2; 3 4] - range —
0:2:10,linspace(a,b,n) - matrix mul —
A*B· elementwiseA.*B - transpose / solve —
A'·A\b - if —
if c … elseif … end - loop —
for k = 1:n … end - anon fn —
f = @(x) x.^2 - function —
function y=f(x) … endfunction - reduce —
sum mean max cumsum