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[2]())
|
112 | '
|
113 |
|
114 | echo 'LOOPS PYTHON'
|
115 | echo
|
116 |
|
117 | # We would have to test multiple blocks in a loop
|
118 | #
|
119 | # for i in (0 .. 3) {
|
120 | # cd /tmp { # this will work
|
121 | # echo $i
|
122 | # }
|
123 | # }
|
124 |
|
125 | python3 -c '
|
126 | def create_functions():
|
127 | funcs = []
|
128 | for i in range(3):
|
129 | # TODO: This is bad!!! Not idiomatic
|
130 | funcs.append(lambda i=i: i) # Using default argument to capture loop variable
|
131 | #funcs.append(lambda: i)
|
132 | return funcs
|
133 |
|
134 | functions = create_functions()
|
135 |
|
136 | for i in range(3):
|
137 | actual = functions[i]()
|
138 | assert i == actual, "%d != %d" % (i, actual)
|
139 |
|
140 | print(functions[2]())
|
141 | '
|
142 | }
|
143 |
|
144 | js-while-var() {
|
145 | echo 'WHILE JS'
|
146 | echo
|
147 |
|
148 | nodejs -e '
|
149 | function createFunctions() {
|
150 | const funcs = [];
|
151 | let i = 0; // for let is SPECIAL!
|
152 | while (i < 3) {
|
153 | funcs.push(function() { return i; });
|
154 | i++;
|
155 | }
|
156 | return funcs;
|
157 | }
|
158 |
|
159 | const functions = createFunctions();
|
160 |
|
161 | console.log(functions[0]())
|
162 | console.log(functions[1]())
|
163 | console.log(functions[2]())
|
164 | '
|
165 |
|
166 | echo 'FOR VAR JS'
|
167 | echo
|
168 |
|
169 | nodejs -e '
|
170 | function createFunctions() {
|
171 | const funcs = [];
|
172 | // var is not captured
|
173 | for (var i = 0; i < 3; i++) {
|
174 | funcs.push(function() { return i; });
|
175 | }
|
176 | return funcs;
|
177 | }
|
178 |
|
179 | const functions = createFunctions();
|
180 |
|
181 | console.log(functions[0]())
|
182 | console.log(functions[1]())
|
183 | console.log(functions[2]())
|
184 | '
|
185 |
|
186 | echo 'FOR LET'
|
187 | echo
|
188 |
|
189 | nodejs -e '
|
190 | function createFunctions() {
|
191 | const funcs = [];
|
192 | for (let i = 0; i < 3; i++) {
|
193 | // This is captured
|
194 | // let j = i + 10;
|
195 |
|
196 | // This is not captured, I guess it is "hoisted"
|
197 | var j = i + 10;
|
198 | funcs.push(function() { return j; });
|
199 | }
|
200 | return funcs;
|
201 | }
|
202 |
|
203 | const functions = createFunctions();
|
204 |
|
205 | console.log(functions[0]())
|
206 | console.log(functions[1]())
|
207 | console.log(functions[2]())
|
208 | '
|
209 | }
|
210 |
|
211 | nested() {
|
212 | echo 'NESTED JS'
|
213 | echo
|
214 |
|
215 | nodejs -e '
|
216 | function outer(x) {
|
217 | return function(y) {
|
218 | return function(z) {
|
219 | return x + y + z;
|
220 | };
|
221 | };
|
222 | }
|
223 | '
|
224 |
|
225 | echo 'NESTED PYTHON'
|
226 | echo
|
227 |
|
228 | python3 -c '
|
229 | def outer(x):
|
230 | def middle(y):
|
231 | def inner(z):
|
232 | return x + y + z
|
233 | return inner
|
234 | return middle
|
235 |
|
236 | nested = outer(1)(2)
|
237 | assert nested(3) == 6, "Test 2 failed"
|
238 | '
|
239 | }
|
240 |
|
241 | value-or-var() {
|
242 | # Good point from HN thread, this doesn't work
|
243 | #
|
244 | # https://news.ycombinator.com/item?id=21095662
|
245 | #
|
246 | # "I think if I were writing a language from scratch, and it included
|
247 | # lambdas, they'd close over values, not variables, and mutating the
|
248 | # closed-over variables would have no effect on the world outside the closure
|
249 | # (or perhaps be disallowed entirely)."
|
250 | #
|
251 | # I think having 'capture' be syntax sugar for value.Obj could do this:
|
252 | #
|
253 | # func f(y) {
|
254 | # var z = {}
|
255 | #
|
256 | # func g(self, x) capture {y, z} -> Int {
|
257 | # return (self.y + x)
|
258 | # }
|
259 | # return (g)
|
260 | # }
|
261 | #
|
262 | # Now you have {y: y, z: z} ==> {__call__: <Func>}
|
263 | #
|
264 | # This would be syntax sugar for:
|
265 | #
|
266 | # func f(y) {
|
267 | # var z = {}
|
268 | #
|
269 | # var attrs = {y, z}
|
270 | # func g(self, x) -> Int {
|
271 | # return (self.y + x)
|
272 | # }
|
273 | # var methods = Object(null, {__call__: g}
|
274 | #
|
275 | # var callable = Object(methods, attrs))
|
276 | # return (callable)
|
277 | # }
|
278 | #
|
279 | # "This mechanism that you suggest about copying values is how Lua used to
|
280 | # work before version 5.0, when they came up with the current upvalue
|
281 | # mechanism"
|
282 | #
|
283 | # I think we could use value.Place if you really want a counter ...
|
284 | #
|
285 | # call counter->setValue(counter.getValue() + 1)
|
286 |
|
287 | echo 'VALUE JS'
|
288 | echo
|
289 |
|
290 | nodejs -e '
|
291 | var x = 42;
|
292 | var f = function () { return x; }
|
293 | x = 43;
|
294 | var g = function () { return x; }
|
295 |
|
296 | console.log(f());
|
297 | console.log(g());
|
298 | '
|
299 |
|
300 | # Hm doesn't work
|
301 | echo
|
302 |
|
303 | nodejs -e '
|
304 | let x = 42;
|
305 | let f = function () { return x; }
|
306 | x = 43;
|
307 | let g = function () { return x; }
|
308 |
|
309 | console.log(f());
|
310 | console.log(g());
|
311 | '
|
312 |
|
313 | echo
|
314 | echo 'VALUE PYTHON'
|
315 | echo
|
316 |
|
317 | python3 -c '
|
318 | x = 42
|
319 | f = lambda: x
|
320 | x = 43
|
321 | g = lambda: x
|
322 |
|
323 | print(f());
|
324 | print(g());
|
325 | '
|
326 |
|
327 | echo
|
328 | echo 'VALUE LUA'
|
329 | echo
|
330 |
|
331 | lua -e '
|
332 | local x = 42
|
333 | local f = function() return x end
|
334 | x = 43
|
335 | local g = function() return x end
|
336 |
|
337 | print(f())
|
338 | print(g())
|
339 | '
|
340 | }
|
341 |
|
342 | # More against closures:
|
343 | #
|
344 | # https://news.ycombinator.com/item?id=22110772
|
345 | #
|
346 | # "I don't understand the intuition of closures and they turn me off to
|
347 | # languages immediately. They feel like a hack from someone who didn't want to
|
348 | # store a copy of a parent-scope variable within a function."
|
349 | #
|
350 | # My question, against local scopes (var vs let in ES6) and closures vs.
|
351 | # classes:
|
352 | #
|
353 | # https://news.ycombinator.com/item?id=15225193
|
354 | #
|
355 | # 1. Modifying collections. map(), filter(), etc. are so much clearer and more
|
356 | # declarative than imperatively transforming a collection.
|
357 |
|
358 | # 2. Callbacks for event handlers or the command pattern. (If you're using a
|
359 | # framework that isn't event based, this may not come up much.)
|
360 |
|
361 | # 3. Wrapping up a bundle of code so that you can defer it, conditionally,
|
362 | # execute it, execute it in a certain context, or do stuff before and after it.
|
363 | # Python's context stuff handles much of this for you, but then that's another
|
364 | # language feature you have to explicitly add.
|
365 |
|
366 | # Minority opinion about closures:
|
367 | #
|
368 | # - C# changed closure-in-loop
|
369 | # - Go changed closure-in-loop
|
370 | # - Lua changed as of 5.0?
|
371 | # - TODO: Test out closures in Lua too
|
372 | #
|
373 | # - Python didn't change it, but people mostly write blog posts about it, and
|
374 | # don't hit it?
|
375 |
|
376 |
|
377 | ruby-blocks() {
|
378 | ruby -e '
|
379 | def create_multiplier(factor)
|
380 | ->(x) { x * factor }
|
381 | end
|
382 |
|
383 | double = create_multiplier(2)
|
384 | triple = create_multiplier(3)
|
385 |
|
386 | puts double.call(5) # Output: 10
|
387 | puts triple.call(5) # Output: 15
|
388 | '
|
389 | echo
|
390 |
|
391 | ruby -e '
|
392 | def use_multiplier(factor)
|
393 | # This method yields to a block
|
394 | yield factor
|
395 | end
|
396 |
|
397 | multiplier = 3
|
398 |
|
399 | # The block captures the outer multiplier variable
|
400 | result = use_multiplier(5) { |x| x * multiplier }
|
401 | puts result # Output: 15
|
402 |
|
403 | # alternative syntax
|
404 | result = use_multiplier(5) do |x|
|
405 | x * multiplier
|
406 | end
|
407 |
|
408 | puts result # Output: 15
|
409 | '
|
410 | echo
|
411 |
|
412 | ruby -e '
|
413 | # alternative syntax
|
414 | def use_multiplier(factor, &block)
|
415 | block.call(factor)
|
416 | end
|
417 |
|
418 | multiplier = 3
|
419 |
|
420 | result = use_multiplier(5) { |x| x * multiplier }
|
421 | puts result # Output: 15
|
422 |
|
423 | # alterantive syntax
|
424 | result = use_multiplier(5) do |x|
|
425 | x * multiplier
|
426 | end
|
427 |
|
428 | puts result # Output: 15
|
429 | '
|
430 | }
|
431 |
|
432 | ruby-mine() {
|
433 | ruby -e '
|
434 | # Two styles
|
435 |
|
436 | # Implicit block arg
|
437 | def run_it
|
438 | yield 2
|
439 | end
|
440 |
|
441 | # explicit proc arg
|
442 | def run_it2 (&block) # interchangeable
|
443 | block.call(2)
|
444 | end
|
445 |
|
446 | # 2 Styles of Block
|
447 |
|
448 | factor = 3
|
449 |
|
450 | block1 = ->(x) { x * factor }
|
451 | puts block1.call(5)
|
452 |
|
453 | result = run_it(&block1)
|
454 | puts result
|
455 |
|
456 | puts
|
457 |
|
458 | g = 9 # visible
|
459 |
|
460 | block2 = lambda do |x|
|
461 | x * factor * g
|
462 | h = 20
|
463 | end
|
464 |
|
465 | # Not visible, but in YSH we may want it to be, e.g. for try { } and shopt { }
|
466 | # puts h
|
467 |
|
468 | puts block2.call(5)
|
469 |
|
470 | result = run_it(&block2)
|
471 | puts result
|
472 |
|
473 | puts
|
474 |
|
475 | # 2 styles of Proc
|
476 |
|
477 | proc1 = proc { |x| x * factor }
|
478 | puts proc1.call(5)
|
479 |
|
480 | result = run_it(&proc1)
|
481 | puts result
|
482 |
|
483 | puts
|
484 |
|
485 | proc2 = Proc.new do |x|
|
486 | x * factor
|
487 | end
|
488 | puts proc2.call(5)
|
489 |
|
490 | result = run_it(&proc2)
|
491 | puts result
|
492 |
|
493 | puts
|
494 |
|
495 | # Now do a literal style
|
496 |
|
497 | result = run_it do |x|
|
498 | x * factor
|
499 | end
|
500 | puts result
|
501 | '
|
502 | }
|
503 |
|
504 | ruby-binding() {
|
505 | ruby demo/survey-closure.rb
|
506 | }
|
507 |
|
508 | "$@"
|