OSDN Git Service

Some edits to some notebooks.
[joypy/Thun.git] / docs / source / notebooks / NerdSnipe.md
1 # Nerd Sniped
2
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.
5
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.
7
8 I figure it might be fun and informative to derive a similar function in Joy (Thun).
9
10 let's start with an example list on the stack:
11
12     joy? [2 7 1 19 18 3]
13     
14     [2 7 1 19 18 3]
15
16 Following the BQN style, let's get the length (`size` in Thun) and create a list of indicies:
17
18     [2 7 1 19 18 3]
19     
20     joy? dup size range
21     
22     [2 7 1 19 18 3] [5 4 3 2 1 0]
23
24 We want to increment each index:
25
26     [2 7 1 19 18 3] [5 4 3 2 1 0]
27     
28     joy? [++] map
29     
30     [2 7 1 19 18 3] [6 5 4 3 2 1]
31
32 But we will also want the length in a function that calculates an index `mod` the length:
33
34     [2 7 1 19 18 3]
35     
36     joy? dup size [swap mod] cons
37     
38     [2 7 1 19 18 3] [6 swap mod]
39
40 Let's do both:
41
42     joy? clear [2 7 1 19 18 3]
43     
44     [2 7 1 19 18 3]
45     
46     joy? dup size [range [++] map] [[swap mod] cons] cleave
47     
48     [2 7 1 19 18 3] [6 5 4 3 2 1] [6 swap mod]
49
50 Okay!  Then `map` the one over the other:
51
52     joy? map
53     
54     [2 7 1 19 18 3] [0 1 2 0 0 0]
55
56 Now we want to make these results into Booleans, but represented as integers (because we are going to multiply by them later):
57
58     [2 7 1 19 18 3] [0 1 2 0 0 0]
59     
60     joy? [0 = [0] [1] branch] map
61     
62     [2 7 1 19 18 3] [1 0 0 1 1 1]
63
64 Clunky, but we now have our list of filter integers. `zip` to transpose the two lists to a list of pairs:
65
66     [2 7 1 19 18 3] [1 0 0 1 1 1]
67     
68     joy? zip
69     
70     [[1 2] [0 7] [0 1] [1 19] [1 18] [1 3]]
71
72 # UGH!  Pairs are in reverse order!  Python vs. our Nice New Definition!
73
74     [[2 1] [7 0] [1 0] [19 1] [18 1] [3 1]]
75
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).
79
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:
81
82     [[1 2] [0 7] [0 1] [1 19] [1 18] [1 3]]
83     
84     joy? [i * sqr] map
85     
86     [4 0 0 361 324 9]
87     
88     joy? sum
89     
90     698
91
92 Oops!  I got the wrong result!  I must have inverted the logic on the mapping to ints above:
93
94     [2 7 1 19 18 3] [0 1 2 0 0 0]
95     
96     joy? [0 = [1] [0] branch] map
97     
98     [2 7 1 19 18 3] [0 1 1 0 0 0]
99
100     joy? zip
101     
102     [[0 2] [1 7] [1 1] [0 19] [0 18] [0 3]]
103     
104     joy? [i * sqr] map
105     
106     [0 49 1 0 0 0]
107     
108     joy? sum
109     
110     50
111
112 Hmm, that's not it.  Maybe I'm indexing the items backwards?
113
114     [2 7 1 19 18 3] [0 1 2 0 0 0]
115     
116     joy? reverse
117     
118     [2 7 1 19 18 3] [0 0 0 2 1 0]
119     
120     joy? [0 = [0] [1] branch] map
121     
122     [2 7 1 19 18 3] [1 1 1 0 0 1]
123     
124     joy? zip
125     
126     [[1 2] [1 7] [1 1] [0 19] [0 18] [1 3]]
127     
128     joy? [i * sqr] map
129     
130     [4 49 1 0 0 9]
131     
132     joy? sum
133     
134     63
135
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.)
137
138 Let's examine this monster in all it's glory:
139
140     dup size [range [++] map] [[swap mod] cons] cleave map reverse [0 = [0] [1] branch] map zip [i * sqr] map sum
141
142 Gnarly, eh?
143
144 Let's reformat:
145
146     dup size
147     [range [++] map] [[swap mod] cons] cleave
148     map
149     reverse
150     [0 = [0] [1] branch] map
151     zip
152     [i * sqr] map
153     sum
154
155
156 ### ≡ `reverse-range-++`
157
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?
159
160     reverse-range-++ == range [++] map reverse
161
162 See: [`range` with `H4` in the Recursion Combinators notebook](https://joypy.osdn.io/notebooks/Recursion_Combinators.html#range-with-h4).
163
164     [reverse-range-++ [] swap [1 <=] [pop] [[swons] dupdip --] tailrec] inscribe
165     
166 Then:
167
168     dup size
169     [reverse-range-++] [[swap mod] cons] cleave
170     map
171     [0 = [0] [1] branch] map
172     zip
173     [i * sqr] map
174     sum
175
176 We can also collapse two of the `map` functions into one:
177
178     dup size
179     [reverse-range-++] [[swap mod 0 = [0] [1] branch] cons] cleave
180     map
181     zip
182     [i * sqr] map
183     sum
184
185 We could extract sub-functions, e.g. `[0] [1] branch` converts Booleans to integers, we might wind up using that again somewhere, eh?
186
187 But really, following the BQN style, this is about as good as it gets.
188
189
190 ## What's it Look Like in Thun?
191
192 That's BQN in Thun, what's it look like in Thun?
193
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`:
195
196     0 swap [sqr +] step-indicies-mod-length
197
198 E.g.:
199
200     0 [2 7 1 19 18 3] [sqr +] step-indicies-mod-length
201
202 So now the problem is to derive an efficient form of `step-indicies-mod-length`
203
204 Let's assume for the moment that we have a function that gives us a predicate for the indicies:
205
206     size [swap mod 0 =] cons
207
208 We run that on the list and then we can make something like this (where `n` is the `size` of the list):
209
210     n swap mod 0 = [pop] [sqr +] branch
211
212 So:
213
214     0 [2 7 1 19 18 3] [F] step
215
216 Except we need a counter for the index!
217
218 ### ≡ `step-enumerate`
219
220 How about a version of `step` that also enumerates the list items?
221
222     [...] [F] step-enumerate
223
224 If the list is empty, it does nothing:
225
226        [] [F] step-enumerate
227     ---------------------------
228
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`:
230
231           [a ...] [F] step-enumerate
232     ---------------------------------------
233        a 0 F [...] [F] 0 step-enumerate′
234
235
236 so
237
238     step-enumerate == over truthy [popop] [FOO] branch
239
240 `FOO`
241
242     [a ...] [F] FOO
243     [a ...] [F] [uncons] dip
244     a [...] [F] 0 roll<
245     a 0 [...] [F] dupdipd
246     a 0 F [...] [F] 0 step-enumerate′
247
248 Okay, so:
249
250     FOO == [uncons] dip 0 roll< dupdipd 0 step-enumerate′
251
252 And therefore:
253
254     [step-enumerate over truthy [popop] [[uncons] dip 0 roll< dupdipd 0 step-enumerate′] branch] inscribe
255
256
257
258 This function is like `step` but it first increments the counter before each evaluation of `F`.
259
260 If the list has become empty, do nothing:
261
262        ... [] [F] n step-enumerate′
263     ----------------------------------
264
265 If there are terms yet in the list it increments the counter and runs `F` with (a copy of) it:
266
267              ... [b ...] [F] n step-enumerate′
268     ---------------------------------------------------
269        ... b (n+1) F [...] [F] (n+1) step-enumerate′
270
271
272 ### A simple General Recursive Function
273
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
279
280 Where:
281
282     P == popop not
283     
284     E == popopop
285
286 Now then:
287
288     ... [b ...] [F] n R0 [step-enumerate′] R1
289
290 Here's what we're trying to accomplish:
291
292         ... [b ...]          [F]   n   R0 [step-enumerate′] R1
293         ...  b (n+1) F [...] [F] (n+1)     step-enumerate′
294
295 `R1` is trivially `i` (so this is a `tailrec` function.)  Let's derive `R0`:
296
297     ... [b ...] [F] n R0
298     ... [b ...] [F] n ++
299     ... [b ...] [F] (n+1)
300
301     ... [b ...] [F] (n+1) [swons] nullary
302     ... [b ...] [F] (n+1) [(n+1) F] 
303
304
305 ### - - - - -
306
307     ... [b ...] [F] (n+1)
308     ... [b ...] [F] (n+1) [uncons] dipd
309     ... [b ...] uncons [F] (n+1)
310     ... b [...] [F] (n+1)
311
312     ... b [...] [F] (n+1) [swons] nullary
313     ... b [...] [F] (n+1) [(n+1) F] dipddd
314     ... b (n+1) F [...] [F] (n+1)
315
316 It looks like we're done:
317
318     R0 == ++ [uncons] dipd [swons] nullary dipddd
319
320 I don't like it, but it should work (provided you write `dipddd`, eh?)
321
322