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 | js-mutate-var-let() {
|
258 | nodejs -e '
|
259 | function outer() {
|
260 | var x = "X_outer";
|
261 | var y = "Y_outer";
|
262 |
|
263 | function inner() {
|
264 | var x = "X_inner";
|
265 | y = "Y_inner";
|
266 |
|
267 | console.log(`inner: x=${x} y=${y}`);
|
268 | }
|
269 |
|
270 | inner();
|
271 |
|
272 | console.log(`outer: x=${x} y=${y}`);
|
273 | }
|
274 |
|
275 | outer();
|
276 | '
|
277 |
|
278 | echo
|
279 |
|
280 | # Does not change eanything
|
281 | nodejs -e '
|
282 | function outer() {
|
283 | let x = "X_outer";
|
284 | let y = "Y_outer";
|
285 |
|
286 | function inner() {
|
287 | let x = "X_inner";
|
288 | y = "Y_inner";
|
289 |
|
290 | console.log(`let inner: x=${x} y=${y}`);
|
291 | }
|
292 |
|
293 | inner();
|
294 |
|
295 | console.log(`let outer: x=${x} y=${y}`);
|
296 | }
|
297 |
|
298 | outer();
|
299 | '
|
300 | }
|
301 |
|
302 | value-or-var() {
|
303 | # Good point from HN thread, this doesn't work
|
304 | #
|
305 | # https://news.ycombinator.com/item?id=21095662
|
306 | #
|
307 | # "I think if I were writing a language from scratch, and it included
|
308 | # lambdas, they'd close over values, not variables, and mutating the
|
309 | # closed-over variables would have no effect on the world outside the closure
|
310 | # (or perhaps be disallowed entirely)."
|
311 | #
|
312 | # I think having 'capture' be syntax sugar for value.Obj could do this:
|
313 | #
|
314 | # func f(y) {
|
315 | # var z = {}
|
316 | #
|
317 | # func g(self, x) capture {y, z} -> Int {
|
318 | # return (self.y + x)
|
319 | # }
|
320 | # return (g)
|
321 | # }
|
322 | #
|
323 | # Now you have {y: y, z: z} ==> {__call__: <Func>}
|
324 | #
|
325 | # This would be syntax sugar for:
|
326 | #
|
327 | # func f(y) {
|
328 | # var z = {}
|
329 | #
|
330 | # var attrs = {y, z}
|
331 | # func g(self, x) -> Int {
|
332 | # return (self.y + x)
|
333 | # }
|
334 | # var methods = Object(null, {__call__: g}
|
335 | #
|
336 | # var callable = Object(methods, attrs))
|
337 | # return (callable)
|
338 | # }
|
339 | #
|
340 | # "This mechanism that you suggest about copying values is how Lua used to
|
341 | # work before version 5.0, when they came up with the current upvalue
|
342 | # mechanism"
|
343 | #
|
344 | # I think we could use value.Place if you really want a counter ...
|
345 | #
|
346 | # call counter->setValue(counter.getValue() + 1)
|
347 |
|
348 | echo 'VALUE JS'
|
349 | echo
|
350 |
|
351 | nodejs -e '
|
352 | var x = 42;
|
353 | var f = function () { return x; }
|
354 | x = 43;
|
355 | var g = function () { return x; }
|
356 |
|
357 | console.log(f());
|
358 | console.log(g());
|
359 | '
|
360 |
|
361 | # Hm doesn't work
|
362 | echo
|
363 |
|
364 | nodejs -e '
|
365 | let x = 42;
|
366 | let f = function () { return x; }
|
367 | x = 43;
|
368 | let g = function () { return x; }
|
369 |
|
370 | console.log(f());
|
371 | console.log(g());
|
372 | '
|
373 |
|
374 | echo
|
375 | echo 'VALUE PYTHON'
|
376 | echo
|
377 |
|
378 | python3 -c '
|
379 | x = 42
|
380 | f = lambda: x
|
381 | x = 43
|
382 | g = lambda: x
|
383 |
|
384 | print(f());
|
385 | print(g());
|
386 | '
|
387 |
|
388 | echo
|
389 | echo 'VALUE LUA'
|
390 | echo
|
391 |
|
392 | lua -e '
|
393 | local x = 42
|
394 | local f = function() return x end
|
395 | x = 43
|
396 | local g = function() return x end
|
397 |
|
398 | print(f())
|
399 | print(g())
|
400 | '
|
401 | }
|
402 |
|
403 | # More against closures:
|
404 | #
|
405 | # https://news.ycombinator.com/item?id=22110772
|
406 | #
|
407 | # "I don't understand the intuition of closures and they turn me off to
|
408 | # languages immediately. They feel like a hack from someone who didn't want to
|
409 | # store a copy of a parent-scope variable within a function."
|
410 | #
|
411 | # My question, against local scopes (var vs let in ES6) and closures vs.
|
412 | # classes:
|
413 | #
|
414 | # https://news.ycombinator.com/item?id=15225193
|
415 | #
|
416 | # 1. Modifying collections. map(), filter(), etc. are so much clearer and more
|
417 | # declarative than imperatively transforming a collection.
|
418 |
|
419 | # 2. Callbacks for event handlers or the command pattern. (If you're using a
|
420 | # framework that isn't event based, this may not come up much.)
|
421 |
|
422 | # 3. Wrapping up a bundle of code so that you can defer it, conditionally,
|
423 | # execute it, execute it in a certain context, or do stuff before and after it.
|
424 | # Python's context stuff handles much of this for you, but then that's another
|
425 | # language feature you have to explicitly add.
|
426 |
|
427 | # Minority opinion about closures:
|
428 | #
|
429 | # - C# changed closure-in-loop
|
430 | # - Go changed closure-in-loop
|
431 | # - Lua changed as of 5.0?
|
432 | # - TODO: Test out closures in Lua too
|
433 | #
|
434 | # - Python didn't change it, but people mostly write blog posts about it, and
|
435 | # don't hit it?
|
436 |
|
437 |
|
438 | ruby-blocks() {
|
439 | ruby -e '
|
440 | def create_multiplier(factor)
|
441 | ->(x) { x * factor }
|
442 | end
|
443 |
|
444 | double = create_multiplier(2)
|
445 | triple = create_multiplier(3)
|
446 |
|
447 | puts double.call(5) # Output: 10
|
448 | puts triple.call(5) # Output: 15
|
449 | '
|
450 | echo
|
451 |
|
452 | ruby -e '
|
453 | def use_multiplier(factor)
|
454 | # This method yields to a block
|
455 | yield factor
|
456 | end
|
457 |
|
458 | multiplier = 3
|
459 |
|
460 | # The block captures the outer multiplier variable
|
461 | result = use_multiplier(5) { |x| x * multiplier }
|
462 | puts result # Output: 15
|
463 |
|
464 | # alternative syntax
|
465 | result = use_multiplier(5) do |x|
|
466 | x * multiplier
|
467 | end
|
468 |
|
469 | puts result # Output: 15
|
470 | '
|
471 | echo
|
472 |
|
473 | ruby -e '
|
474 | # alternative syntax
|
475 | def use_multiplier(factor, &block)
|
476 | block.call(factor)
|
477 | end
|
478 |
|
479 | multiplier = 3
|
480 |
|
481 | result = use_multiplier(5) { |x| x * multiplier }
|
482 | puts result # Output: 15
|
483 |
|
484 | # alterantive syntax
|
485 | result = use_multiplier(5) do |x|
|
486 | x * multiplier
|
487 | end
|
488 |
|
489 | puts result # Output: 15
|
490 | '
|
491 | }
|
492 |
|
493 | ruby-mine() {
|
494 | ruby -e '
|
495 | # Two styles
|
496 |
|
497 | # Implicit block arg
|
498 | def run_it
|
499 | yield 2
|
500 | end
|
501 |
|
502 | # explicit proc arg
|
503 | def run_it2 (&block) # interchangeable
|
504 | block.call(2)
|
505 | end
|
506 |
|
507 | # 2 Styles of Block
|
508 |
|
509 | factor = 3
|
510 |
|
511 | block1 = ->(x) { x * factor }
|
512 | puts block1.call(5)
|
513 |
|
514 | result = run_it(&block1)
|
515 | puts result
|
516 |
|
517 | puts
|
518 |
|
519 | g = 9 # visible
|
520 |
|
521 | block2 = lambda do |x|
|
522 | x * factor * g
|
523 | h = 20
|
524 | end
|
525 |
|
526 | # Not visible, but in YSH we may want it to be, e.g. for try { } and shopt { }
|
527 | # puts h
|
528 |
|
529 | puts block2.call(5)
|
530 |
|
531 | result = run_it(&block2)
|
532 | puts result
|
533 |
|
534 | puts
|
535 |
|
536 | # 2 styles of Proc
|
537 |
|
538 | proc1 = proc { |x| x * factor }
|
539 | puts proc1.call(5)
|
540 |
|
541 | result = run_it(&proc1)
|
542 | puts result
|
543 |
|
544 | puts
|
545 |
|
546 | proc2 = Proc.new do |x|
|
547 | x * factor
|
548 | end
|
549 | puts proc2.call(5)
|
550 |
|
551 | result = run_it(&proc2)
|
552 | puts result
|
553 |
|
554 | puts
|
555 |
|
556 | # Now do a literal style
|
557 |
|
558 | result = run_it do |x|
|
559 | x * factor
|
560 | end
|
561 | puts result
|
562 | '
|
563 | }
|
564 |
|
565 | ruby-binding() {
|
566 | ruby demo/survey-closure.rb
|
567 | }
|
568 |
|
569 | "$@"
|