diff --git a/arr.sml b/arr.sml
index 964175648688783ef44ca9ff00300922ed44b9ef..e8faf3c14573abcf9c0ab06cd18e42056d4f89e3 100644
--- a/arr.sml
+++ b/arr.sml
@@ -10,26 +10,50 @@              | Nd of arr * arr (* (val, next) *)
              | Bx of arr * arr (* (shape, arr) *)
              | Zl (* Zilde *)
 with
+(* K exceptions *)
 exception Domain;
 exception Index;
 exception BadStop;
 exception NYI;
+(* atom constructors *)
 fun Word(x) = Lf(N(x));
 fun Fun0(f) = Lf(FN(f));
 fun Fun1(f) = Lf(FM(f));
 fun Fun2(f) = Lf(FD(f));
+(* array constructor *)
 fun Array([]) = Zl
   | Array([x]) = x
   | Array(x::xs) = Nd(x,Array(xs)); (* TODO: automatically box? *)
+(* return tree-element type *)
+fun tt Zl = "Zl"
+  | tt (Lf(x)) = "Lf"
+  | tt (Nd(x,y)) = "Nd"
+  | tt (Bx(s,a)) = "Bx"
+  | tt _ = raise Domain;
+(* convert array back to list *)
+fun unarray a = 
+  let fun leaves Zl = []
+        | leaves (Lf(x)) = [Lf(x)]
+        | leaves (Nd(x,y)) = x::(unarray y)
+        | leaves (Bx(x,y)) = unarray y
+  in leaves a (* map (fn Lf(x) => x) (leaves a) *)
+  end;
+(* unbox a boxed array *)
 fun unbox(Bx(s,a)) = a
   | unbox(x) = x;
+(* return length of array *)
 fun tally(Zl) = 0w0
   | tally(Lf(a:atom)) = 0w1
   | tally(Nd(x,y)) = 0w1 + tally y
   | tally(Bx(s,a)) = tally (unbox a);
+(* box an array *)
 fun Box(a:arr) = Bx(Array([Word(tally a)]),a);
+(* return shape of boxed array *)
+(* TODO: do we only want this for boxed arrays?  do we want just tally for unboxed arrays? *)
+(* TODO: Do we want a boxed array to be length 1 or the length of the contents? *)
 fun shape(Bx(s,a)) = Box(s)
-  | shape(Nd(x,y)) = Box(Array([Word(tally(Nd(x,y)))])); (* TODO: Do we want a boxed array to be length 1 of the length of the contents? *)
+  | shape(Nd(x,y)) = Box(Array([Word(tally(Nd(x,y)))]));
+(* return a string representation of an array *)
 fun str(Zl) = "(Zilde)" (* TODO: box-drawing characters might be nice in the future *)
   | str(Lf(N(x))) = Word8.fmt StringCvt.HEX x
   | str(Lf(FN(x))) = "(Nilad)" (* TODO: would be nice to get actual function names *)
@@ -37,19 +61,37 @@   | str(Lf(FM(x))) = "(Monad)"
   | str(Lf(FD(x))) = "(Dyad)"
   | str(Nd(x,y)) = str(x) ^ " " ^ str(y)
   | str(Bx(s,a)) = "[" ^ str(a) ^ "]";
+(* return a string representation of the internal structure of an array *)
+fun tree(Zl) = "X"
+  | tree(Lf(N(x))) = "LEAF{" ^ (Word8.fmt StringCvt.HEX x) ^ "}"
+  | tree(Lf(FN(x))) = "(Nilad)" (* TODO: would be nice to get actual function names *)
+  | tree(Lf(FM(x))) = "(Monad)"
+  | tree(Lf(FD(x))) = "(Dyad)"
+  | tree(Nd(x,y)) =  "NODE{" ^ tree(x) ^ ", " ^ tree(y) ^ "}"
+  | tree(Bx(x,y)) = "----------\n" ^ tree(y) ^ "\n----------\n";
+(* call a nilad *)
 (* TODO: higher-order functions *)
 fun apply0(f) = raise NYI; (* TODO - what if it generates a whole array? *)
+(* apply a monad to an array *)
+(* TODO: higher-order functions *)
+(* TODO: apply to an atom? *)
 fun apply1(f,Zl) = raise Domain  (* traverse the array tree, applying f to each leaf in turn *)
   | apply1(f,Lf(N(x))) = Lf(N(f(x)))
   | apply1(f,Nd(x,y)) = Nd(apply1(f,x),apply1(f,y));
+(* apply a dyad to two arrays *)
+(* TODO: higher-order functions *)
+(* TODO: apply to atoms, or a mix of both?  do we need tagged functions for e.g. # that changes dependent on atom or array? *)
 fun apply2(f,x,y) = raise NYI; (* TODO *)
+(* append an element to an array *)
 fun append(x,a) = Nd(x,a);
+(* return an element at an index *)
 fun index _ Zl = Zl (* TODO: Is this the behaviour that we want?  Or should it raise an exception? *)
   | index 0w0 (Lf(x)) = Lf(x)
   | index _ (Lf(x)) = raise Index
   | index 0w0 (Nd(x,y)) = x
   | index i (Nd(x,y)) = index (i-0w1) y
   | index i (Bx(s,a)) = index i a;
+(* take the first n elements from an array *)
 fun take _ Zl = Zl
   | take 0w0 a = raise Index
   | take 0w1 (Lf(x)) = Lf(x)
@@ -57,14 +99,17 @@   | take 0w1 (Bx(s,a)) = raise NYI (* TODO: implement taking from first axis like dyalog? *)
   | take 0w1 (Nd(x,y)) = x
   | take n (Nd(x,y)) = Nd(x,(take (n-0w1) y))
   | take n (Bx(x,y)) = raise NYI; (* TODO: see above *)
+(* drop the first n elements from an array *)
 fun drop _ Zl = Zl
   | drop 0w0 a = a
   | drop 0w1 (Nd(x,y)) = y
   | drop n (Nd(x,y)) = drop (n-0w1) y
   | drop _ (Bx(s,a)) = raise NYI; (* TODO: see notes on boxes on take above *)
-fun reverse (Lf(x)) = Lf(x) (* FIXME: thsi doesn't behave properly, see str(take 0w4 (reverse (iota 0w8))); *)
+(* reverse an array *)
+fun reverse (Lf(x)) = Lf(x) (* FIXME: this doesn't behave properly, see str(take 0w4 (reverse (iota 0w8))); *)
   | reverse (Bx(s,a)) = Bx(s,a) (* TODO: boxing behaviour might be inconsistent across functions; check/correct asap before you get too deep into it *)
   | reverse (Nd(x,y)) = Nd((reverse y),x);
+(* return the first n integers (zero-indexed) *)
 fun iota n =
   let fun i 0w0 = raise Index (* TODO: is this the right exception? *)
         | i 0w1 = Word(0w0)
