1 | #!/usr/bin/env bash
|
2 | #
|
3 | # Survey closures, with a bunch of comments/notes
|
4 | #
|
5 | # Usage:
|
6 | # demo/survey-closure.sh <function name>
|
7 |
|
8 | set -o nounset
|
9 | set -o pipefail
|
10 | set -o errexit
|
11 |
|
12 | source build/dev-shell.sh # python3 in $PATH
|
13 |
|
14 | counter() {
|
15 | echo 'COUNTER JS'
|
16 | echo
|
17 |
|
18 | nodejs -e '
|
19 | function createCounter() {
|
20 | let count = 0;
|
21 | return function() {
|
22 | // console.log("after", after);
|
23 | count++;
|
24 | return count;
|
25 | };
|
26 | let after = 42;
|
27 | }
|
28 |
|
29 | const counter = createCounter();
|
30 | console.assert(counter() === 1, "Test 1.1 failed");
|
31 | console.assert(counter() === 2, "Test 1.2 failed");
|
32 |
|
33 | console.log(counter());
|
34 | '
|
35 |
|
36 | echo 'COUNTER PYTHON'
|
37 | echo
|
38 |
|
39 | python3 -c '
|
40 | def create_counter():
|
41 | count = 0
|
42 | def counter():
|
43 | # Python lets you do this!
|
44 | #print("after", after);
|
45 | nonlocal count
|
46 | count += 1
|
47 | return count
|
48 | after = 42
|
49 | return counter
|
50 |
|
51 | counter = create_counter()
|
52 | assert counter() == 1, "Test 1.1 failed"
|
53 | assert counter() == 2, "Test 1.2 failed"
|
54 |
|
55 | print(counter())
|
56 | '
|
57 | }
|
58 |
|
59 | # The famous C# / Go issue, and the design note at the end:
|
60 | #
|
61 | # http://craftinginterpreters.com/closures.html
|
62 | #
|
63 | # "If a language has a higher-level iterator-based looping structure like
|
64 | # foreach in C#, Java’s “enhanced for”, for-of in JavaScript, for-in in Dart,
|
65 | # etc., then I think it’s natural to the reader to have each iteration create a
|
66 | # new variable. The code looks like a new variable because the loop header
|
67 | # looks like a variable declaration."
|
68 | #
|
69 | # I am Python-minded and I think of it as mutating the same location ...
|
70 | #
|
71 | # "If you dig around StackOverflow and other places, you find evidence that
|
72 | # this is what users expect, because they are very surprised when they don’t
|
73 | # get it."
|
74 | #
|
75 | # I think this depends on which languages they came from
|
76 | #
|
77 | # JavaScript var vs. let is a good counterpoint ...
|
78 | #
|
79 | # Another solution for us is to make it explicit:
|
80 | #
|
81 | # captured var x = 1
|
82 | #
|
83 | # "The pragmatically useful answer is probably to do what JavaScript does with
|
84 | # let in for loops. Make it look like mutation but actually create a new
|
85 | # variable each time, because that’s what users want. It is kind of weird when
|
86 | # you think about it, though."
|
87 | #
|
88 | # Ruby has TWO different behaviors, shown there:
|
89 | #
|
90 | # - for i in 1..2 - this is mutable
|
91 | # - (1..2).each do |i| ... - this creates a new variable
|
92 |
|
93 | loops() {
|
94 | echo 'LOOPS JS'
|
95 | echo
|
96 |
|
97 | nodejs -e '
|
98 | function createFunctions() {
|
99 | const funcs = [];
|
100 | for (let i = 0; i < 3; i++) {
|
101 | funcs.push(function() { return i; });
|
102 | }
|
103 | return funcs;
|
104 | }
|
105 |
|
106 | const functions = createFunctions();
|
107 | console.assert(functions[0]() === 0, "Test 4.1 failed");
|
108 | console.assert(functions[1]() === 1, "Test 4.2 failed");
|
109 | console.assert(functions[2]() === 2, "Test 4.3 failed");
|
110 |
|
111 | console.log(functions[1]())
|
112 | '
|
113 |
|
114 | # similar to proc p example
|
115 | echo
|
116 | echo 'JS define function'
|
117 | echo
|
118 |
|
119 | nodejs -e '
|
120 | for (let i = 0; i < 5; i++) {
|
121 | // this works, is it like let?
|
122 | function inner() { return i; }
|
123 | // let inner = function() { return i; }
|
124 | }
|
125 | console.log("INNER");
|
126 | console.log(inner());
|
127 | '
|
128 |
|
129 | echo
|
130 | echo 'LOOPS PYTHON'
|
131 | echo
|
132 |
|
133 | # We would have to test multiple blocks in a loop
|
134 | #
|
135 | # for i in (0 .. 3) {
|
136 | # cd /tmp { # this will work
|
137 | # echo $i
|
138 | # }
|
139 | # }
|
140 |
|
141 | python3 -c '
|
142 | def create_functions():
|
143 | funcs = []
|
144 | for i in range(3):
|
145 | # TODO: This is bad!!! Not idiomatic
|
146 | funcs.append(lambda i=i: i) # Using default argument to capture loop variable
|
147 | #funcs.append(lambda: i)
|
148 | return funcs
|
149 |
|
150 | functions = create_functions()
|
151 |
|
152 | for i in range(3):
|
153 | actual = functions[i]()
|
154 | assert i == actual, "%d != %d" % (i, actual)
|
155 |
|
156 | print(functions[1]())
|
157 | '
|
158 | }
|
159 |
|
160 | js-while-var() {
|
161 | echo 'WHILE JS'
|
162 | echo
|
163 |
|
164 | nodejs -e '
|
165 | function createFunctions() {
|
166 | const funcs = [];
|
167 | let i = 0; // for let is SPECIAL!
|
168 | while (i < 3) {
|
169 | funcs.push(function() { return i; });
|
170 | i++;
|
171 | }
|
172 | return funcs;
|
173 | }
|
174 |
|
175 | const functions = createFunctions();
|
176 |
|
177 | console.log(functions[0]())
|
178 | console.log(functions[1]())
|
179 | console.log(functions[2]())
|
180 | '
|
181 |
|
182 | echo 'FOR VAR JS'
|
183 | echo
|
184 |
|
185 | nodejs -e '
|
186 | function createFunctions() {
|
187 | const funcs = [];
|
188 | // var is not captured
|
189 | for (var i = 0; i < 3; i++) {
|
190 | funcs.push(function() { return i; });
|
191 | }
|
192 | return funcs;
|
193 | }
|
194 |
|
195 | const functions = createFunctions();
|
196 |
|
197 | console.log(functions[0]())
|
198 | console.log(functions[1]())
|
199 | console.log(functions[2]())
|
200 | '
|
201 |
|
202 | echo 'FOR LET'
|
203 | echo
|
204 |
|
205 | nodejs -e '
|
206 | function createFunctions() {
|
207 | const funcs = [];
|
208 | for (let i = 0; i < 3; i++) {
|
209 | // This is captured
|
210 | // let j = i + 10;
|
211 |
|
212 | // This is not captured, I guess it is "hoisted"
|
213 | var j = i + 10;
|
214 | funcs.push(function() { return j; });
|
215 | }
|
216 | return funcs;
|
217 | }
|
218 |
|
219 | const functions = createFunctions();
|
220 |
|
221 | console.log(functions[0]())
|
222 | console.log(functions[1]())
|
223 | console.log(functions[2]())
|
224 | '
|
225 | }
|
226 |
|
227 | nested() {
|
228 | echo 'NESTED JS'
|
229 | echo
|
230 |
|
231 | nodejs -e '
|
232 | function outer(x) {
|
233 | return function(y) {
|
234 | return function(z) {
|
235 | return x + y + z;
|
236 | };
|
237 | };
|
238 | }
|
239 | '
|
240 |
|
241 | echo 'NESTED PYTHON'
|
242 | echo
|
243 |
|
244 | python3 -c '
|
245 | def outer(x):
|
246 | def middle(y):
|
247 | def inner(z):
|
248 | return x + y + z
|
249 | return inner
|
250 | return middle
|
251 |
|
252 | nested = outer(1)(2)
|
253 | assert nested(3) == 6, "Test 2 failed"
|
254 | '
|
255 | }
|
256 |
|
257 | value-or-var() {
|
258 | # Good point from HN thread, this doesn't work
|
259 | #
|
260 | # https://news.ycombinator.com/item?id=21095662
|
261 | #
|
262 | # "I think if I were writing a language from scratch, and it included
|
263 | # lambdas, they'd close over values, not variables, and mutating the
|
264 | # closed-over variables would have no effect on the world outside the closure
|
265 | # (or perhaps be disallowed entirely)."
|
266 | #
|
267 | # I think having 'capture' be syntax sugar for value.Obj could do this:
|
268 | #
|
269 | # func f(y) {
|
270 | # var z = {}
|
271 | #
|
272 | # func g(self, x) capture {y, z} -> Int {
|
273 | # return (self.y + x)
|
274 | # }
|
275 | # return (g)
|
276 | # }
|
277 | #
|
278 | # Now you have {y: y, z: z} ==> {__call__: <Func>}
|
279 | #
|
280 | # This would be syntax sugar for:
|
281 | #
|
282 | # func f(y) {
|
283 | # var z = {}
|
284 | #
|
285 | # var attrs = {y, z}
|
286 | # func g(self, x) -> Int {
|
287 | # return (self.y + x)
|
288 | # }
|
289 | # var methods = Object(null, {__call__: g}
|
290 | #
|
291 | # var callable = Object(methods, attrs))
|
292 | # return (callable)
|
293 | # }
|
294 | #
|
295 | # "This mechanism that you suggest about copying values is how Lua used to
|
296 | # work before version 5.0, when they came up with the current upvalue
|
297 | # mechanism"
|
298 | #
|
299 | # I think we could use value.Place if you really want a counter ...
|
300 | #
|
301 | # call counter->setValue(counter.getValue() + 1)
|
302 |
|
303 | echo 'VALUE JS'
|
304 | echo
|
305 |
|
306 | nodejs -e '
|
307 | var x = 42;
|
308 | var f = function () { return x; }
|
309 | x = 43;
|
310 | var g = function () { return x; }
|
311 |
|
312 | console.log(f());
|
313 | console.log(g());
|
314 | '
|
315 |
|
316 | # Hm doesn't work
|
317 | echo
|
318 |
|
319 | nodejs -e '
|
320 | let x = 42;
|
321 | let f = function () { return x; }
|
322 | x = 43;
|
323 | let g = function () { return x; }
|
324 |
|
325 | console.log(f());
|
326 | console.log(g());
|
327 | '
|
328 |
|
329 | echo
|
330 | echo 'VALUE PYTHON'
|
331 | echo
|
332 |
|
333 | python3 -c '
|
334 | x = 42
|
335 | f = lambda: x
|
336 | x = 43
|
337 | g = lambda: x
|
338 |
|
339 | print(f());
|
340 | print(g());
|
341 | '
|
342 |
|
343 | echo
|
344 | echo 'VALUE LUA'
|
345 | echo
|
346 |
|
347 | lua -e '
|
348 | local x = 42
|
349 | local f = function() return x end
|
350 | x = 43
|
351 | local g = function() return x end
|
352 |
|
353 | print(f())
|
354 | print(g())
|
355 | '
|
356 | }
|
357 |
|
358 | # More against closures:
|
359 | #
|
360 | # https://news.ycombinator.com/item?id=22110772
|
361 | #
|
362 | # "I don't understand the intuition of closures and they turn me off to
|
363 | # languages immediately. They feel like a hack from someone who didn't want to
|
364 | # store a copy of a parent-scope variable within a function."
|
365 | #
|
366 | # My question, against local scopes (var vs let in ES6) and closures vs.
|
367 | # classes:
|
368 | #
|
369 | # https://news.ycombinator.com/item?id=15225193
|
370 | #
|
371 | # 1. Modifying collections. map(), filter(), etc. are so much clearer and more
|
372 | # declarative than imperatively transforming a collection.
|
373 |
|
374 | # 2. Callbacks for event handlers or the command pattern. (If you're using a
|
375 | # framework that isn't event based, this may not come up much.)
|
376 |
|
377 | # 3. Wrapping up a bundle of code so that you can defer it, conditionally,
|
378 | # execute it, execute it in a certain context, or do stuff before and after it.
|
379 | # Python's context stuff handles much of this for you, but then that's another
|
380 | # language feature you have to explicitly add.
|
381 |
|
382 | # Minority opinion about closures:
|
383 | #
|
384 | # - C# changed closure-in-loop
|
385 | # - Go changed closure-in-loop
|
386 | # - Lua changed as of 5.0?
|
387 | # - TODO: Test out closures in Lua too
|
388 | #
|
389 | # - Python didn't change it, but people mostly write blog posts about it, and
|
390 | # don't hit it?
|
391 |
|
392 |
|
393 | ruby-blocks() {
|
394 | ruby -e '
|
395 | def create_multiplier(factor)
|
396 | ->(x) { x * factor }
|
397 | end
|
398 |
|
399 | double = create_multiplier(2)
|
400 | triple = create_multiplier(3)
|
401 |
|
402 | puts double.call(5) # Output: 10
|
403 | puts triple.call(5) # Output: 15
|
404 | '
|
405 | echo
|
406 |
|
407 | ruby -e '
|
408 | def use_multiplier(factor)
|
409 | # This method yields to a block
|
410 | yield factor
|
411 | end
|
412 |
|
413 | multiplier = 3
|
414 |
|
415 | # The block captures the outer multiplier variable
|
416 | result = use_multiplier(5) { |x| x * multiplier }
|
417 | puts result # Output: 15
|
418 |
|
419 | # alternative syntax
|
420 | result = use_multiplier(5) do |x|
|
421 | x * multiplier
|
422 | end
|
423 |
|
424 | puts result # Output: 15
|
425 | '
|
426 | echo
|
427 |
|
428 | ruby -e '
|
429 | # alternative syntax
|
430 | def use_multiplier(factor, &block)
|
431 | block.call(factor)
|
432 | end
|
433 |
|
434 | multiplier = 3
|
435 |
|
436 | result = use_multiplier(5) { |x| x * multiplier }
|
437 | puts result # Output: 15
|
438 |
|
439 | # alterantive syntax
|
440 | result = use_multiplier(5) do |x|
|
441 | x * multiplier
|
442 | end
|
443 |
|
444 | puts result # Output: 15
|
445 | '
|
446 | }
|
447 |
|
448 | ruby-mine() {
|
449 | ruby -e '
|
450 | # Two styles
|
451 |
|
452 | # Implicit block arg
|
453 | def run_it
|
454 | yield 2
|
455 | end
|
456 |
|
457 | # explicit proc arg
|
458 | def run_it2 (&block) # interchangeable
|
459 | block.call(2)
|
460 | end
|
461 |
|
462 | # 2 Styles of Block
|
463 |
|
464 | factor = 3
|
465 |
|
466 | block1 = ->(x) { x * factor }
|
467 | puts block1.call(5)
|
468 |
|
469 | result = run_it(&block1)
|
470 | puts result
|
471 |
|
472 | puts
|
473 |
|
474 | g = 9 # visible
|
475 |
|
476 | block2 = lambda do |x|
|
477 | x * factor * g
|
478 | h = 20
|
479 | end
|
480 |
|
481 | # Not visible, but in YSH we may want it to be, e.g. for try { } and shopt { }
|
482 | # puts h
|
483 |
|
484 | puts block2.call(5)
|
485 |
|
486 | result = run_it(&block2)
|
487 | puts result
|
488 |
|
489 | puts
|
490 |
|
491 | # 2 styles of Proc
|
492 |
|
493 | proc1 = proc { |x| x * factor }
|
494 | puts proc1.call(5)
|
495 |
|
496 | result = run_it(&proc1)
|
497 | puts result
|
498 |
|
499 | puts
|
500 |
|
501 | proc2 = Proc.new do |x|
|
502 | x * factor
|
503 | end
|
504 | puts proc2.call(5)
|
505 |
|
506 | result = run_it(&proc2)
|
507 | puts result
|
508 |
|
509 | puts
|
510 |
|
511 | # Now do a literal style
|
512 |
|
513 | result = run_it do |x|
|
514 | x * factor
|
515 | end
|
516 | puts result
|
517 | '
|
518 | }
|
519 |
|
520 | ruby-binding() {
|
521 | ruby demo/survey-closure.rb
|
522 | }
|
523 |
|
524 | "$@"
|