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 | # I think this is the thing that Go and C# changed!
|
118 | # Gah
|
119 | #
|
120 | # We would have to test multiple blocks in a loop
|
121 | #
|
122 | # for i in (0 .. 3) {
|
123 | # cd /tmp { # this will work
|
124 | # echo $i
|
125 | # }
|
126 | #
|
127 | # var b = ^(echo $i)
|
128 | # call blocks->append(b) # won't work
|
129 | # }
|
130 |
|
131 | python3 -c '
|
132 | def create_functions():
|
133 | funcs = []
|
134 | for i in range(3):
|
135 | # TODO: This is bad!!! Not idiomatic
|
136 | funcs.append(lambda i=i: i) # Using default argument to capture loop variable
|
137 | #funcs.append(lambda: i)
|
138 | return funcs
|
139 |
|
140 | functions = create_functions()
|
141 |
|
142 | for i in range(3):
|
143 | actual = functions[i]()
|
144 | assert i == actual, "%d != %d" % (i, actual)
|
145 |
|
146 | print(functions[2]())
|
147 | '
|
148 | }
|
149 |
|
150 | nested() {
|
151 | echo 'NESTED JS'
|
152 | echo
|
153 |
|
154 | nodejs -e '
|
155 | function outer(x) {
|
156 | return function(y) {
|
157 | return function(z) {
|
158 | return x + y + z;
|
159 | };
|
160 | };
|
161 | }
|
162 | '
|
163 |
|
164 | echo 'NESTED PYTHON'
|
165 | echo
|
166 |
|
167 | python3 -c '
|
168 | def outer(x):
|
169 | def middle(y):
|
170 | def inner(z):
|
171 | return x + y + z
|
172 | return inner
|
173 | return middle
|
174 |
|
175 | nested = outer(1)(2)
|
176 | assert nested(3) == 6, "Test 2 failed"
|
177 | '
|
178 | }
|
179 |
|
180 | value-or-var() {
|
181 | # Good point from HN thread, this doesn't work
|
182 | #
|
183 | # https://news.ycombinator.com/item?id=21095662
|
184 | #
|
185 | # "I think if I were writing a language from scratch, and it included
|
186 | # lambdas, they'd close over values, not variables, and mutating the
|
187 | # closed-over variables would have no effect on the world outside the closure
|
188 | # (or perhaps be disallowed entirely)."
|
189 | #
|
190 | # I think having 'capture' be syntax sugar for value.Obj could do this:
|
191 | #
|
192 | # func f(y) {
|
193 | # var z = {}
|
194 | #
|
195 | # func g(self, x) capture {y, z} -> Int {
|
196 | # return (self.y + x)
|
197 | # }
|
198 | # return (g)
|
199 | # }
|
200 | #
|
201 | # Now you have {y: y, z: z} ==> {__call__: <Func>}
|
202 | #
|
203 | # This would be syntax sugar for:
|
204 | #
|
205 | # func f(y) {
|
206 | # var z = {}
|
207 | #
|
208 | # var attrs = {y, z}
|
209 | # func g(self, x) -> Int {
|
210 | # return (self.y + x)
|
211 | # }
|
212 | # var methods = Object(null, {__call__: g}
|
213 | #
|
214 | # var callable = Object(methods, attrs))
|
215 | # return (callable)
|
216 | # }
|
217 | #
|
218 | # "This mechanism that you suggest about copying values is how Lua used to
|
219 | # work before version 5.0, when they came up with the current upvalue
|
220 | # mechanism"
|
221 | #
|
222 | # I think we could use value.Place if you really want a counter ...
|
223 | #
|
224 | # call counter->setValue(counter.getValue() + 1)
|
225 |
|
226 | echo 'VALUE JS'
|
227 | echo
|
228 |
|
229 | nodejs -e '
|
230 | var x = 42;
|
231 | var f = function () { return x; }
|
232 | x = 43;
|
233 | var g = function () { return x; }
|
234 |
|
235 | console.log(f());
|
236 | console.log(g());
|
237 | '
|
238 |
|
239 | # Hm doesn't work
|
240 | echo
|
241 |
|
242 | nodejs -e '
|
243 | let x = 42;
|
244 | let f = function () { return x; }
|
245 | x = 43;
|
246 | let g = function () { return x; }
|
247 |
|
248 | console.log(f());
|
249 | console.log(g());
|
250 | '
|
251 |
|
252 | echo
|
253 | echo 'VALUE PYTHON'
|
254 | echo
|
255 |
|
256 | python3 -c '
|
257 | x = 42
|
258 | f = lambda: x
|
259 | x = 43
|
260 | g = lambda: x
|
261 |
|
262 | print(f());
|
263 | print(g());
|
264 | '
|
265 |
|
266 | echo
|
267 | echo 'VALUE LUA'
|
268 | echo
|
269 |
|
270 | lua -e '
|
271 | local x = 42
|
272 | local f = function() return x end
|
273 | x = 43
|
274 | local g = function() return x end
|
275 |
|
276 | print(f())
|
277 | print(g())
|
278 | '
|
279 | }
|
280 |
|
281 | # More against closures:
|
282 | #
|
283 | # https://news.ycombinator.com/item?id=22110772
|
284 | #
|
285 | # "I don't understand the intuition of closures and they turn me off to
|
286 | # languages immediately. They feel like a hack from someone who didn't want to
|
287 | # store a copy of a parent-scope variable within a function."
|
288 | #
|
289 | # My question, against local scopes (var vs let in ES6) and closures vs.
|
290 | # classes:
|
291 | #
|
292 | # https://news.ycombinator.com/item?id=15225193
|
293 | #
|
294 | # 1. Modifying collections. map(), filter(), etc. are so much clearer and more
|
295 | # declarative than imperatively transforming a collection.
|
296 |
|
297 | # 2. Callbacks for event handlers or the command pattern. (If you're using a
|
298 | # framework that isn't event based, this may not come up much.)
|
299 |
|
300 | # 3. Wrapping up a bundle of code so that you can defer it, conditionally,
|
301 | # execute it, execute it in a certain context, or do stuff before and after it.
|
302 | # Python's context stuff handles much of this for you, but then that's another
|
303 | # language feature you have to explicitly add.
|
304 |
|
305 | # Minority opinion about closures:
|
306 | #
|
307 | # - C# changed closure-in-loop
|
308 | # - Go changed closure-in-loop
|
309 | # - Lua changed as of 5.0?
|
310 | # - TODO: Test out closures in Lua too
|
311 | #
|
312 | # - Python didn't change it, but people mostly write blog posts about it, and
|
313 | # don't hit it?
|
314 |
|
315 | "$@"
|