Use "canonical form" strategy to break down function type. If we want to construct a term t that type A → B, the term t has to have the form λx.t0 where x (the argument) has type A and t0 (the body) has type B (in ATS, it would be written as lam x => t0).

If A is a function type, that means x is a function given to us.

If B is a function type, then recursively break down B using the canonical form strategy.

If B is not a function type, then look at all the arguments we are given so far (some of them are functions), and see if there is any combination of the function call that will give us B.

Sometimes it may be useful to do function composition to obtain the required type, i.e. given f: A → B and g: B → C, if we want a function that has the type A → C, then we can write λx.g (f x).

Use the typing rules of lam and app to verify your result is correct.

Motivated why these typing rules are written the way they are.

Operational semantics t ⇒ t' means that t is reduced in one step to t'. Imagine your program is a very long expression which runs by reducing to smaller programs until it becomes a value, which is irreducible, representing the result of computation. The only rule we're interested here concerns function call: (λx.t0) t ⇒ t0[x := t], by substitution all occurrences of variable x inside the function body with the term passed in as the argument. This is called β reduction.

Call by value: evaluate argument t to a value first, then substitute for function call. Call by name: substitute t into the function body, and evaluate individual occurrences of t separately. If t is an expression that prints out a string, call by value only prints once prior to the function call, and call by name prints as many times as x occurs in the function.

Substitution lemma: if Γ, x : A ⊢ t0 : B and Γ ⊢ t : A, then Γ ⊢ t0[x := t] : B. This lemma describes what's the correct behavior for β reduction, which governs how lam and app rules can be written if we want the type system to be sound.

If you use the lrand48() function in "libc/SATS/random.sats", then you probably need a way to convert from lint (long int) to int. There is a primitive function called atspre_int_of_lint defined in "prelude/CATS/integer.cats" but there is no function type declaration in the corresponding "prelude/SATS/integer.sats". In order to use it in your code, add this to your .dats file:

ralist is analogous to binary representation of natural number (bignum) like singly linked list is analogous to unary representation.

Unary representation: empty string is zero, successor adds one by prepending a digit to the string, predecessor removes a digit from the beginning of the string.

Singly linked list represents a sequence of data by placing one element at each digit. Empty list is analogous to zero, cons analogous to successor, uncons (pattern matching) analogous to predessor.

Binary representation: still a list of digits, least significant digit first so carrying is easier to implement. Each digit is either odd (1) or even (0).

Random access list represents a sequence of data by grouping them into a forest of balanced binary trees. If the i-th digit is odd, then that digit carries a balanced binary tree of size 2i. The length of the data sequence is the binary number represented by odd and even.

Racons is analogous to successor (needs to build the tree by carrying) and rauncons to the predecessor (needs destruct by borrowing).

The easy way to implement ralookup and raupdate is to keep track of the indices skipped to the respective size of the trees, and to perform a binary tree lookup/update at the correct digit.

The idea is that we recursively form pairs as we go down the digits. The pairing guarantee completely balanced binary tree. The key to lookup and update is to treat the rest of the list indexed in pairs. This is the approach taken by the solution.

Efficient update, however, requires a "map" function f: a → a. At the very top level, it updates only an element. As we recurse down the digits, we build the pair update function f': '(a, a) → '(a, a), which takes a pair, and according to the index, updates either the left or the right a using f. To go down one more digit, the update function becomes f'': '('(a, a), '(a, a)) → '('(a, a), '(a, a)) and so on.

Tail recursion is preferred when scanning a list left-to-right, and the result does not specify a particular order. If the result must be in the same order as input, then non-tail is easier to implement. If tail-recursion is desired and result must be in the same order as input, then the accumulator, which is constructed by prepending elements from left-to-right, needs to be reversed. Do not use list append, as it is O(n) for each element, which makes the whole function O(n2).

When traversing the whole binary tree, tail recursion is still possible, but we need to use continuation passing style. For example, in post-order traversal, when traversing the left subtree, we pass it a continuation to tell it what to do when that's done (i.e. should traverse the right tree). When traversing the right tree, we pass it a continuation to tell what to do when that's done (i.e. do something with the parent). A continuation is a function closure that is called by the base case of recursion, taking the accumulator (which is the partial result) as the argument. The continuation would perform further computation on the accumulator.

Printf works like C printf, but is type checked. The error message looks like this: the needed type is vararg, the actual type is ... . Not perfectly informative, but vararg helps pointing out it's an error involving the format string.

There is a difference between '(x1, x2, ..., xn) and @(x1, x2, ..., xn) tuples.