3 There's [an interesting video by Conor Hoekstra](https://youtu.be/JELcdZLre3s?t=2748)
4 wherein he presents an example function implemented in BQN.
6 It's the Sum of Squares of a list of numbers filtered by whether or not the index of each item in the list modulus the length of list. It's a weird thing to do, unlikely to come up in practice, but that's alright, it's just to exercise the language.
8 I figure it might be fun and informative to derive a similar function in Joy (Thun).
10 let's start with an example list on the stack:
16 Following the BQN style, let's get the length (`size` in Thun) and create a list of indicies:
22 [2 7 1 19 18 3] [5 4 3 2 1 0]
24 We want to increment each index:
26 [2 7 1 19 18 3] [5 4 3 2 1 0]
30 [2 7 1 19 18 3] [6 5 4 3 2 1]
32 But we will also want the length in a function that calculates an index `mod` the length:
36 joy? dup size [swap mod] cons
38 [2 7 1 19 18 3] [6 swap mod]
42 joy? clear [2 7 1 19 18 3]
46 joy? dup size [range [++] map] [[swap mod] cons] cleave
48 [2 7 1 19 18 3] [6 5 4 3 2 1] [6 swap mod]
50 Okay! Then `map` the one over the other:
54 [2 7 1 19 18 3] [0 1 2 0 0 0]
56 Now we want to make these results into Booleans, but represented as integers (because we are going to multiply by them later):
58 [2 7 1 19 18 3] [0 1 2 0 0 0]
60 joy? [0 = [0] [1] branch] map
62 [2 7 1 19 18 3] [1 0 0 1 1 1]
64 Clunky, but we now have our list of filter integers. `zip` to transpose the two lists to a list of pairs:
66 [2 7 1 19 18 3] [1 0 0 1 1 1]
70 [[1 2] [0 7] [0 1] [1 19] [1 18] [1 3]]
72 # UGH! Pairs are in reverse order! Python vs. our Nice New Definition!
74 [[2 1] [7 0] [1 0] [19 1] [18 1] [3 1]]
76 See [DeriveZip notebook](/notebooks/DeriveZip.html). Not that it matters
77 for this application because we are about to multiply these pairs and,
78 of course, multiplication is [commutative](https://en.wikipedia.org/wiki/Commutative).
80 Now, for each pair we want to multiply the pairs (using the duality of Booleans as integers to convert the numbers we want to work on to themselves and the numbers we do not want to work on to zero which is the --I don't recall the jargon!-- zero times anything is zero, and zero plus anything is that thing, and we use that in a moment to get around actually filtering our list!) As I was saying we multiply the pairs, then square the result, then sum all the results:
82 [[1 2] [0 7] [0 1] [1 19] [1 18] [1 3]]
92 Oops! I got the wrong result! I must have inverted the logic on the mapping to ints above:
94 [2 7 1 19 18 3] [0 1 2 0 0 0]
96 joy? [0 = [1] [0] branch] map
98 [2 7 1 19 18 3] [0 1 1 0 0 0]
102 [[0 2] [1 7] [1 1] [0 19] [0 18] [0 3]]
112 Hmm, that's not it. Maybe I'm indexing the items backwards?
114 [2 7 1 19 18 3] [0 1 2 0 0 0]
118 [2 7 1 19 18 3] [0 0 0 2 1 0]
120 joy? [0 = [0] [1] branch] map
122 [2 7 1 19 18 3] [1 1 1 0 0 1]
126 [[1 2] [1 7] [1 1] [0 19] [0 18] [1 3]]
136 Ah! That was it. How do you like my debugging strategy here? Rather than reviewing the original to see that I was getting the right result at each step I just guessed at the "polarity" or "chirality" of the two operations that were (mildly, perhaps) ambiguous. It had to be one or the other, the function is too simple to hide many opportunities for confusion.)
138 Let's examine this monster in all it's glory:
140 dup size [range [++] map] [[swap mod] cons] cleave map reverse [0 = [0] [1] branch] map zip [i * sqr] map sum
147 [range [++] map] [[swap mod] cons] cleave
150 [0 = [0] [1] branch] map
156 ### ≡ `reverse-range-++`
158 Let's golf it a little. There is a version of `range` that generates its result list in reverse order, which would allow us to get rid of that `reverse` in the expression, and I bet we could modify that to generate them already incremented too. Let's assume we've done that already and call it `reverse-range-++`, why not?
160 reverse-range-++ == range [++] map reverse
162 See: [`range` with `H4` in the Recursion Combinators notebook](https://joypy.osdn.io/notebooks/Recursion_Combinators.html#range-with-h4).
164 [reverse-range-++ [] swap [1 <=] [pop] [[swons] dupdip --] tailrec] inscribe
169 [reverse-range-++] [[swap mod] cons] cleave
171 [0 = [0] [1] branch] map
176 We can also collapse two of the `map` functions into one:
179 [reverse-range-++] [[swap mod 0 = [0] [1] branch] cons] cleave
185 We could extract sub-functions, e.g. `[0] [1] branch` converts Booleans to integers, we might wind up using that again somewhere, eh?
187 But really, following the BQN style, this is about as good as it gets.
190 ## What's it Look Like in Thun?
192 That's BQN in Thun, what's it look like in Thun?
194 One way to approach it is to simplify the desired function in terms of another function, and then to derive that other function. We know that the desired function is a "catamorphism" (roughly, it accepts a list and returns a single value) and we know that the "combining function" will be `sqr +` with some filter. So let's start with a specialization of `step`:
196 0 swap [sqr +] step-indicies-mod-length
200 0 [2 7 1 19 18 3] [sqr +] step-indicies-mod-length
202 So now the problem is to derive an efficient form of `step-indicies-mod-length`
204 Let's assume for the moment that we have a function that gives us a predicate for the indicies:
206 size [swap mod 0 =] cons
208 We run that on the list and then we can make something like this (where `n` is the `size` of the list):
210 n swap mod 0 = [pop] [sqr +] branch
214 0 [2 7 1 19 18 3] [F] step
216 Except we need a counter for the index!
218 ### ≡ `step-enumerate`
220 How about a version of `step` that also enumerates the list items?
222 [...] [F] step-enumerate
224 If the list is empty, it does nothing:
226 [] [F] step-enumerate
227 ---------------------------
229 But if the list has members it starts another function with an index counter initialized to zero. Since we know the list has at least one element we can set up the first evaluation of `F`:
231 [a ...] [F] step-enumerate
232 ---------------------------------------
233 a 0 F [...] [F] 0 step-enumerate′
238 step-enumerate == over truthy [popop] [FOO] branch
243 [a ...] [F] [uncons] dip
245 a 0 [...] [F] dupdipd
246 a 0 F [...] [F] 0 step-enumerate′
250 FOO == [uncons] dip 0 roll< dupdipd 0 step-enumerate′
254 [step-enumerate over truthy [popop] [[uncons] dip 0 roll< dupdipd 0 step-enumerate′] branch] inscribe
258 This function is like `step` but it first increments the counter before each evaluation of `F`.
260 If the list has become empty, do nothing:
262 ... [] [F] n step-enumerate′
263 ----------------------------------
265 If there are terms yet in the list it increments the counter and runs `F` with (a copy of) it:
267 ... [b ...] [F] n step-enumerate′
268 ---------------------------------------------------
269 ... b (n+1) F [...] [F] (n+1) step-enumerate′
272 ### A simple General Recursive Function
274 ... [] [F] n step-enumerate′
275 -------------------------------------------
276 ... [] [F] n [P] [T] [R0] [R1] genrec
277 ---------------------------------------------------------
278 ... [] [F] n [P] [T] [R0 [step-enumerate′] R1] ifte
288 ... [b ...] [F] n R0 [step-enumerate′] R1
290 Here's what we're trying to accomplish:
292 ... [b ...] [F] n R0 [step-enumerate′] R1
293 ... b (n+1) F [...] [F] (n+1) step-enumerate′
295 `R1` is trivially `i` (so this is a `tailrec` function.) Let's derive `R0`:
299 ... [b ...] [F] (n+1)
301 ... [b ...] [F] (n+1) [swons] nullary
302 ... [b ...] [F] (n+1) [(n+1) F]
307 ... [b ...] [F] (n+1)
308 ... [b ...] [F] (n+1) [uncons] dipd
309 ... [b ...] uncons [F] (n+1)
310 ... b [...] [F] (n+1)
312 ... b [...] [F] (n+1) [swons] nullary
313 ... b [...] [F] (n+1) [(n+1) F] dipddd
314 ... b (n+1) F [...] [F] (n+1)
316 It looks like we're done:
318 R0 == ++ [uncons] dipd [swons] nullary dipddd
320 I don't like it, but it should work (provided you write `dipddd`, eh?)