00:26 | <shu> | bradleymeck: Buffer seems like it might be a big issue here, or at least the Buffer polyfill |
00:31 | <bradleymeck> | I could believe it, but I also am skeptical of it's actual usage |
00:31 | <bradleymeck> | The only big API people use on it is slice from node side which is what it is emulating |
00:34 | <shu> | bradleymeck: slice is already overridden; i think usage of .map and .filter on Buffers should be rare |
00:34 | <shu> | bradleymeck: so that leaves .subarray, which is the same semantics as Buffer#slice in node at least |
00:34 | <shu> | any intuition on whether people use subarray over slice? |
00:35 | <bradleymeck> | mmmm, i think slice is the most common, but no clue it only takes 1 super popular package to skew results |
00:36 | <shu> | well, for node itself i guess it's "fine" so far as its buffer.js is updated in lockstep with V8 releases |
00:36 | <bradleymeck> | i think we could see usage % of that polyfill and then go back and do a more in depth check of what methods are being used of it |
00:36 | <shu> | oh? how do we see that? |
00:37 | <bradleymeck> | shu: we can check Node's gzemnid DB to scan the npm registry then do popularity queries |
00:37 | <bradleymeck> | but for old deploys it won't help |
00:38 | <bradleymeck> | i am more interested in a crawl of the pages with the buffer polyfill on w/e browser domain |
00:38 | <shu> | hm |
00:38 | <shu> | i am not good enough for http archive but it seems possible to craft some kind of query |
00:38 | <shu> | err, good enough to craft a query for http archive |
00:39 | <bradleymeck> | maybe MylesBorins has experience enough here if you can craft the hueristic to look for |
00:39 | <bradleymeck> | i know he has done some bigquery stuff before |
02:43 | <ljharb> | shu: the buffer polyfill is pulled in any time literally any npm module references `Buffer` |
02:43 | <ljharb> | tape, or deep-equal, or qs, i forget which, has to some kind of crazy hack to have an `isBuffer` impl without forcing the entire buffer polyfill into people's bundles |
02:44 | <shu> | indeed |
02:44 | <shu> | the question is whether they use the inherited methods that create new Uint8Arrays |
02:44 | <shu> | s/they/there is significant |
03:08 | <devsnek> | idk why one would map or filter a buffer |
03:41 | <shu> | yeah that's my hunch too, it comes down to subarray |
03:44 | <devsnek> | subarray is popular |
03:44 | <devsnek> | well idk if its popular in those projects |
03:44 | <devsnek> | but people use it to pass to textdecoders and stuff |
03:44 | <devsnek> | or textencoders |
03:44 | <devsnek> | one of those |
04:36 | <shu> | passing to TextDecoder would be fine as it’s just treating the subarrayed Buffer as a Uint8Array |
04:36 | <shu> | unless this is some Node-specific TextDecoder that takes Buffers |
04:43 | <devsnek> | no its standardized |
16:17 | <davepoole> | rkirsling! thanks, I wasn't sure what the expectation was. :) |
19:40 | <shu> | bradleymeck: ystartsev: littledan: fyi i wrote up a taxonomy of subclassing (thanks to domenic): https://github.com/syg/proposal-rm-builtin-subclassing#taxonomy-of-subclassing |
19:41 | <bradleymeck> | i'm still poking around on if there are any actual usages except feature detections before finalizing a detection script |
19:48 | <devsnek> | we need more proposals with `rm` in the name |
19:49 | <rkirsling> | I like the ㊟ |
19:50 | <shu> | :) |
19:50 | <devsnek> | why is delegating to `this.constructor` not sufficient |
19:51 | <shu> | what does "sufficient" mean? |
19:51 | <devsnek> | said another way, why did @@species ever exist |
19:51 | <shu> | because MS tried to ship just delegating to `this.constructor` and it broke stuff |
19:51 | <bradleymeck> | devsnek: well I've uncovered all sorts of hellish stuff with false positives |
19:51 | <bradleymeck> | yea ^ |
19:51 | <devsnek> | aw |
19:51 | <bradleymeck> | Array.prototype.map.call(window, String, 1) is a thing apparently |
19:52 | <bradleymeck> | IDK WHAT IT MEANS |
19:52 | <bradleymeck> | what are you doing |
19:52 | <shu> | to be clear: broke stuff that expected, pre ES6, to always return e.g. an Array |
19:52 | <bradleymeck> | [].slice.call(arguments) is also a big thing, same for NodeList |
19:52 | <bradleymeck> | lots of those |
19:52 | <devsnek> | ok and for the static promise methods |
19:53 | <devsnek> | i feel like people rely on those doing delegation to `this` |
19:53 | <shu> | those were added in ES6 and were given the delegation treatment out of consistency |
19:53 | <shu> | are there actual subclasses of Promise that depend on the delegation in the constructor methods? |
19:54 | <devsnek> | i'm fairly sure i've seen that |
19:54 | <devsnek> | but i'd have to double check |
19:54 | <shu> | i haven't really, citations would be good |
19:55 | <rkirsling> | wowsers, I'd finished reading through this before but I didn't realize about Type III |
19:55 | <bradleymeck> | extending Promise is super rare with a bunch of regexp checks against httparchive |
19:56 | <shu> | Type III and IV are the "delenda est" types but unfortunately Type II already incurs a big cost :( |
19:56 | <rkirsling> | yeah |
19:56 | <shu> | dan proposed an alternative in issue #1, would be interesting to think through if we can support Type II somehow without the burden |
19:59 | <Bakkot> | shu: there's another category, not yet relevant, which is whether built-in methods delegate to property lookups on their _arguments_, rather than looking into their slots directly (e.g. would (new Set).union(myFakeSet)` look up `Symbol.iterator` on `myFakeSet`) |
19:59 | <shu> | ah interesting |
20:00 | <bradleymeck> | is that really a species concern? |
20:00 | <bradleymeck> | or just a protocol concern |
20:00 | <Bakkot> | no, not really, but neither is type IV in the above taxonomy |
20:01 | <shu> | well, is it a *subclassing* concern |
20:01 | <Bakkot> | I have more feelings about this one because you can't work around it by overriding enough methods, which you can for all the other types |
20:01 | <shu> | the reason Type IV is included is because actions on builtin regexps are unreasonably difficult to optimize because there are hooks on via `this.exec` or whatever |
20:02 | <shu> | Bakkot: i can squint and look at your type and sweep it under the rug as "not harmful" in the way that RegExp[@@match] isn't so bad if you just look at it as a protocol that String uses, and not RegExp built-ins themselves use |
20:03 | <shu> | Bakkot: union(arg) accepting any iterable arg is reasonable and not too much burden for implementations |
20:03 | <shu> | conceptually you basically specced a union that takes Sets, and an overload that takes iterables |
20:04 | <shu> | you have a single decision point of going to the slow path that's easy-ish to stay on |
20:05 | <Bakkot> | shu: `difference` is maybe a more interesting case, because there you want to invoke `.has` on your argument, not `[Symbol.iterator]` |
20:05 | <shu> | ah |
20:05 | <shu> | yeah that's harder |
20:05 | <Bakkot> | still a protocol, just using a named property instead of a symbol one |
20:06 | <Bakkot> | (I guess a third way is to add `Symbol.has`) |
20:07 | <shu> | i think my preference here is to be to require explicit casts via Set() |
20:07 | <shu> | s/to be// |
20:07 | <ljharb> | i actually prefer constructor hooks, a la bradley's map proposal |
20:07 | <shu> | remind me what that is? |
20:07 | <ljharb> | then no methods need to be overridden ever, you'd just provide different hooks |
20:07 | <ljharb> | like `super(iterable, { toValue() {} })` etc |
20:08 | <shu> | hmm, seems scary |
20:08 | <ljharb> | for example, i could see Map and Set accepting a hook that lets you use something besides SameValueZero for comparison |
20:08 | <Bakkot> | shu: :(, that means you can't use a subclass without big-O penalties |
20:09 | <shu> | Bakkot: yeah, i can live with a protocol |
20:09 | <Bakkot> | ljharb historically "you can't add functionality on top of the built in things, you just have to trust the language designers to have added all the things you might need already" has not been a good philosophy, I think |
20:09 | <ljharb> | oh totally agree |
20:09 | <shu> | but might ask there be explicit in-spec fast paths then for passing actual Sets |
20:09 | <ljharb> | and i think you should be *able* to add new things |
20:09 | <ljharb> | but i also think that all the use cases of *overriding* existing things could be done with hooks |
20:10 | <Bakkot> | shu: I feel like engines could just do the actual property gets, confirm they're the built-in ones, and then go to the fast path |
20:10 | <Bakkot> | no? |
20:10 | <Bakkot> | if that's so I wouldn't think the spec would need to add the fast paths |
20:10 | <shu> | Bakkot: i was thinking the fast path would have no observable get of .has |
20:11 | <shu> | Bakkot: it'd consult the internal slots of the Set only |
20:11 | <Bakkot> | ljharb of course they could be done with _enough_ hooks, but, like, I don't think we should trust that we can add the things one might reasonably need |
20:11 | <Bakkot> | ljharb as another example I think it is perfectly reasonable to make a thing which is not a Set at all under the hood, but conforms to the interface, which could be passed as an argument to Set.prototype.union |
20:12 | <ljharb> | Bakkot: i totally agree that hooks would never be sufficient |
20:12 | <ljharb> | i don't agree with that last point tho |
20:12 | <ljharb> | things that accept arraylikes, versus things that accept arrays, are different things |
20:12 | <Bakkot> | shu that would make me sad but it wouldn't be the end of the world |
20:12 | <Bakkot> | ljharb Arrays are magic; they are not a good example to follow. |
20:13 | <ljharb> | ok, things that accept thenables are different than things that accept promises |
20:13 | <ljharb> | or toStringables vs strings, etc |
20:13 | <shu> | Bakkot: isn't this the same reason RegExp subclassing is bad now? maybe difference reasonly only has one such override point with `.has`, but it's a death by thousand cuts kind of thing |
20:13 | <shu> | hm something went wrong with the input field there |
20:13 | <ljharb> | Bakkot: at any rate it's also kind of that ^ that it's fine if "the interface" is one method, but not fine if it's N |
20:15 | <Bakkot> | shu I think the tradeoffs are different, basically. I don't think there's that much utility in making a fake regexp, and there people care about absolutely maximum speed, whereas there is a lot of utility in making a Set-like, and the speed is not quite so much of a concern |
20:16 | <Bakkot> | ljharb I basically do not understand what analogy you are trying to make |
20:16 | <ljharb> | hm, ok |
20:16 | <shu> | Bakkot: crucially, a lot of utility in making a Set-like by actually subclassing Set and overriding e.g. has |
20:16 | <shu> | not a completely custom Set-like |
20:16 | <ljharb> | (there's also utility in doing that and not having `Set.prototype.has.call` provide different behavior) |
20:17 | <Bakkot> | shu two things: 1.) if you do that, then the fast-path has to use the userland has, and 2.) I don't agree actually; I think the "wraps a Set" vs "extends Set" distinction is basically an implementation detail and either approach is reasonable |
20:17 | <Bakkot> | ljharb I don't think users should think about Set.prototype.has.call basically ever |
20:18 | <Bakkot> | shu s/the fast path has to use the userland has/there cannot be a fast-path which avoids observable .has lookup/ |
20:20 | <shu> | Bakkot: hm, i'd have to think more about the "speed is not quite so much of a concern" |
20:20 | <shu> | ofc none of this is an issue if speed is not a concern |
20:20 | <shu> | well, not quite true |
20:20 | <shu> | but this would all be decidedly less concerning, at least |
20:23 | <shu> | Bakkot: reading through the current Set code i remain leaning towards another way to provide custom 'has' functionality than overriding the method |
20:23 | <shu> | Bakkot: one can reasonably expect Set#delete or Set#add to invoke Set#has, but it does not |
20:24 | <Bakkot> | > one can reasonably expect Set#delete or Set#add to invoke Set#has |
20:24 | <Bakkot> | ... can one? |
20:24 | <Bakkot> | by contrast, the set constructor itself does invoke Set#add |
20:24 | <shu> | that is certainly a good point |
20:24 | <Bakkot> | (which falls under your type 4 taxonomy) |
20:25 | <Bakkot> | shu: but also, as I say I am more concerned with invoking these methods on _arguments_ than on `this`; for `this` you can always get around it by overriding all the methods |
20:27 | <shu> | i can certainly see the argument that different data structures have different expectations of being subclassable piecemeal |
21:26 | <rkirsling> | shu: btw you mentioned trying to deal with a single method as an experiment, but I think for us it's ArraySpeciesCreate as a whole that results in greatest implementation complexity |
21:27 | <shu> | rkirsling: yeah, good point, should've been more precise in what i meant |
21:27 | <rkirsling> | (mind you I'm not used to digging into this particular area so there could be more stuff that's just not jumping out as obviously, but most of the rest just seems to be reflecting the spec itself, whereas our DFG has watchpoints for array species) |
21:27 | <shu> | rkirsling: it'd be good to look at the diff of an implementation of a single builtin under two scenarios: both types II and III removed, and only type III removed |
21:28 | <rkirsling> | yeah that's fair |
21:28 | <shu> | rkirsling: for V8 at least, the stuff that looks out for species looks out for more than species, despite the name, which might be a gotcha |
21:28 | <rkirsling> | ahh yeah, could be |
23:07 | <rkirsling> | TabAtkins: wow, that's the most positive subdiscussion I've seen in the pipeline repo in recent memory |
23:08 | <rkirsling> | #yay |
23:10 | <TabAtkins> | yeah it's weird how heated people get |
23:13 | <Bakkot> | rkirsling which? |
23:14 | <rkirsling> | last two comments at https://github.com/tc39/proposal-pipeline-operator/issues/167 |
23:15 | <rkirsling> | makes me kinda excited |
23:15 | <rkirsling> | let's do this |
23:16 | <ljharb> | TabAtkins: is the style you prefer the `x => await => y` form where x and y are functions? or is it a different one |
23:16 | <TabAtkins> | That's F#-style, so no |
23:17 | <ljharb> | hm, ok |
23:17 | <TabAtkins> | I'm going to be advocating for plain-jane Hack-style, where the RHS is just "any expression", and there's a binding for the # variable over it (bound to the LHS). |
23:17 | <ljharb> | littledan's frameworks outreach call today seemed to prefer that one iirc |
23:17 | <rkirsling> | that's what point (6) is about |
23:17 | <TabAtkins> | littledan is going to be advocating for that, yeah |
23:17 | <ljharb> | so you'd do `await #` if that's the semantic you wanted? |
23:18 | <TabAtkins> | Put the await wherever you need it, yeah. |
23:18 | <ljharb> | (i don't like # as the placeholder choice there but obv that's a stage 2 debate) |
23:18 | <TabAtkins> | (Rather than being forced to put it by itself on a pipeline step) |
23:18 | <ljharb> | right |
23:18 | <TabAtkins> | `val |> async(#) |> foo(await #)` if that's the clearest way to express your intent, for instance |
23:18 | <ljharb> | and for those who prefer the F# style, you could immediately invoke "the function you used in F#" to use it in "hack-style"? |
23:19 | <TabAtkins> | `val |> asyncFn(#) |> foo(await #)`, rather |
23:19 | <TabAtkins> | Yeah, you just call it like you would in normal JS. |
23:19 | <TabAtkins> | If normal JS would have you do `let x = foo(1,2)(3)`, the hack-style pipeline does `3 |> foo(1,2)(#)` |
23:19 | <rkirsling> | `|> await` is certainly the part that makes me frown the most about the other approach |
23:20 | <rkirsling> | your point (7) is strangely compellingly worded for being as obvious as it is though |
23:20 | <ljharb> | TabAtkins: and expressions are lazily or eagerly evaluated? |
23:20 | <Bakkot> | TabAtkins if you present on this, can you contrast to just writing `$ = val; $ = asyncFn($); $=foo(await $)`? |
23:20 | <TabAtkins> | rkirsling: It's apparently not obvious! I've polished it over time talking with other people! |
23:20 | <rkirsling> | :) |
23:20 | <rkirsling> | good work |
23:21 | <TabAtkins> | ljharb: eagerly, it's *exactly* as if you'd taken the LHS and put it in the RHS in place of the # |
23:21 | <TabAtkins> | (module the fact that the LHS is only executed once even if you use # multiple times) |
23:21 | <Bakkot> | TabAtkins wait that would be very surprising to me |
23:21 | <Bakkot> | lazily seems the obvious thing |
23:21 | <ljharb> | yeah that surprises me too |
23:21 | <TabAtkins> | Bakkot: Basically that exact example is the main *competitor* to pipeline ^_^ |
23:21 | <TabAtkins> | wait now i'm surprised |
23:21 | <devsnek> | wait what does lazy mean in this context |
23:21 | <TabAtkins> | that's the same in every pipeline |
23:21 | <Bakkot> | if I don't hit a step in the pipeline, I don't expect to see side effects from the hitting that step |
23:22 | <ljharb> | `console.log(1) |> await # |> console.log(2)` |
23:22 | <TabAtkins> | OH |
23:22 | <ljharb> | i would expect 2 not to log until after the tick |
23:22 | <TabAtkins> | ok yeah |
23:22 | <rkirsling> | yeah I think there was a misspeak? |
23:22 | <ljharb> | that's lazily |
23:22 | <ljharb> | (i believe) |
23:22 | <TabAtkins> | Yeah, LHS is executed, *then* pipeline body is. |
23:23 | <TabAtkins> | You literally can't execute the RHS until you're done with the LHS. |
23:23 | <ljharb> | right |
23:23 | <Bakkot> | TabAtkins re "that exact example is the main *competitor* to pipeline" - right, so, contrasting to the main competitor seems like it would be valuable in your presentation |
23:23 | <devsnek> | its like normal evaluation |
23:23 | <ljharb> | presumably they're parsed eagerly but evaluated lazily |
23:23 | <ljharb> | right |
23:23 | <devsnek> | except with a different delimiter |
23:24 | <devsnek> | i'd be interested to see some codebases where pipelining comes in handy |
23:24 | <TabAtkins> | Bakkot: Yup, explaining the benefit of pipeline over "just write JS without pipeline" is of course a necessary piece of this ^_^ |
23:24 | <devsnek> | i don't think i've ever had something that felt ungangly and in need of a pipeline |
23:24 | <devsnek> | ungainly* |
23:25 | <Bakkot> | TabAtkins well, it's more dramatic for Hack style than F# style, since in F# style you are composing functions directly, whereas in Hack style you're just having a binding created for you - that is, Hack style is much closer to that sample than F# style is |
23:25 | <TabAtkins> | The benefit actually ends up *relatively* small; it's not a world-changer like arrow-function syntax. But it does let you write some common code patterns in a cleaner way, and importantly, lets you get the benefit of doing complex manipulations of a value via multiple statements, but in an expression context. |
23:25 | <rkirsling> | I think anywhere you have 2+ nested calls it could be potentially nicer |
23:25 | <ljharb> | devsnek: react HOCs is a big one |
23:25 | <ljharb> | devsnek: currently it's `withA(withB(withC(withD(Component))))` |
23:26 | <rkirsling> | yuck |
23:26 | <Bakkot> | "lets you get the benefit of doing complex manipulations of a value via multiple statements": mm, sometimes, but only if you don't need intermediate values multiple times; do expressions seem like a much more general solution to that problem |
23:26 | <devsnek> | like trying to extend multiple things? |
23:26 | <ljharb> | devsnek: `Component |> withD |> withC |> withB |> withA` is much clearer |
23:26 | <ljharb> | devsnek: it's not extending, it's composing/wrapping, but yes |
23:26 | <ljharb> | HOC is a "higher-order component" |
23:26 | <devsnek> | seems like a weird pattern |
23:27 | <TabAtkins> | Bakkot: Yup, cut the gordian knot of do exprs and I'd be okay with dropping pipelines ^_^ |
23:27 | <ljharb> | devsnek: it's also how every single usage of redux in the react world worked, prior to hooks. it's a common pattern. |
23:27 | <devsnek> | i've never really done any frontend stuff |
23:28 | <devsnek> | because i hear that patterns like HOC are a thing |
23:28 | <TabAtkins> | lessee, from the HOC documentation: |
23:28 | <TabAtkins> | https://reactjs.org/docs/higher-order-components.html |
23:28 | <devsnek> | i really want to move do expressions forward |
23:28 | <TabAtkins> | const CommentListWithSubscription = withSubscription( |
23:28 | <TabAtkins> | CommentList, |
23:28 | <TabAtkins> | (DataSource) => DataSource.getComments() |
23:28 | <TabAtkins> | ); |
23:28 | <rkirsling> | > the HOC doc |
23:28 | <rkirsling> | FTFY |
23:29 | <rkirsling> | devsnek: just `do` it |
23:29 | <TabAtkins> | would be `const CommentListWithSubscription = CommentList |> withSubscription(#, DataSource=>DataSource.getComments());` |
23:29 | <devsnek> | but people think that control flow inside a block that is part of a do expression is not good |
23:29 | <Bakkot> | devsnek some people (like me) think that, but other people disagree |
23:29 | <TabAtkins> | I can *definitely* see how confusing that would be with another withX() wrapper or two |
23:29 | <Bakkot> | devsnek completion values are also a sticking point |
23:30 | <Bakkot> | devsnek though, it occurs to me you could just require the last statement in the `do` be an expression, so it'd be obvious... |
23:30 | <devsnek> | iirc completion values were less of a blocker and more of a "fine assuming we make sure completions in the spec are clean" |
23:30 | <Bakkot> | devsnek ehhh: https://github.com/tc39/proposal-do-expressions/issues/21 |
23:31 | <devsnek> | i wouldn't like to force that |
23:31 | <ljharb> | devsnek: i mean, it'd be useful in express middlewares too, i'd imagine |
23:31 | <devsnek> | isn't express middleware flat |
23:31 | <devsnek> | `app.use(x); app.use(y)` |
23:31 | <ljharb> | atm yes |
23:31 | <TabAtkins> | Bakkot: Forcing that means I don't get the nice clean "turn if() into an expression by wrapping it in a do{}" functionality :( |
23:31 | <ljharb> | (ftr i also think control flow in do expression blocks is bad) |
23:32 | <devsnek> | TabAtkins: yeah i want that too lol |
23:32 | <Bakkot> | TabAtkins hm, true |
23:32 | <devsnek> | in any case my argument was that people use repls |
23:32 | <devsnek> | which have the same semantics as do expressions |
23:33 | <devsnek> | and aside from block/object parsing, they seem ok with how those work |
23:33 | <devsnek> | people also use completion of scripts with the node vm module |
23:33 | <Bakkot> | people are OK with repls because they don't care what the completion value of a declaration or a loop is |
23:34 | <devsnek> | but it tells them what it is |
23:34 | <ljharb> | ^ that |
23:34 | <ljharb> | sure |
23:34 | <devsnek> | and they don't go on twitter raving about it |
23:34 | <ljharb> | but that doesn't mean they pay attention to it |
23:34 | <Bakkot> | devsnek yeah but that doesn't mean they know |
23:34 | <devsnek> | afaik |
23:34 | <ljharb> | lol the completion value of `console.log` is undefined, and that actually *does* confuse a ton of people, they ask on irc a lot |
23:34 | <TabAtkins> | I will say that I've written plenty of for loops in my repl and never once realized consciously that it even has a value |
23:35 | <devsnek> | github puts an enormous green checkmark on the highest rated comment now |
23:35 | <devsnek> | like it solves the issue or smth |
23:35 | <Bakkot> | alternative possibility: the last statement cannot be a loop or declaration |
23:35 | <ljharb> | devsnek: huh, link? |
23:35 | <devsnek> | not being a declaration makes sense |
23:35 | <rkirsling> | devsnek: highest _rated_? |
23:35 | <Bakkot> | defining "last" is a little weird though |
23:35 | <devsnek> | https://github.com/tc39/proposal-do-expressions/issues/21#issuecomment-359160212 |
23:35 | <devsnek> | but i would use loops too |
23:36 | <Bakkot> | I do not see a giant checkmark |
23:36 | <rkirsling> | nor do I |
23:36 | <Bakkot> | devsnek ... would you? why? |
23:36 | <ljharb> | devsnek: i do not see that. github chrome extension? |
23:36 | <ljharb> | devsnek: maybe you're using "refined github" |
23:36 | <devsnek> | i am using refined github |
23:36 | <Bakkot> | why would anyone ever want the completion value of a loop? |
23:36 | <devsnek> | it could be that i guess |
23:36 | <ljharb> | it does a bunch of weird things, that must be one of them |
23:36 | <devsnek> | Bakkot: because they can have values |
23:36 | <devsnek> | and i want to use that |
23:36 | <Bakkot> | devsnek specifically when? |
23:36 | <Bakkot> | when do you want to use that? |
23:36 | <devsnek> | idk |
23:37 | <Bakkot> | :| |
23:37 | <devsnek> | sometimes you have to factor a loop out |
23:37 | <devsnek> | to use `return` from the body |
23:37 | <Bakkot> | fwiw a _lot_ of people get confused by loops in React not making an array |
23:37 | <devsnek> | or assign to a variable |
23:37 | <Bakkot> | like, endlessly they are confused by this |
23:37 | <TabAtkins> | Bakkot: completion value of a loop is *basically* the return value of a .reduce() |
23:37 | <devsnek> | "loops in react not making an array" what? |
23:37 | <Bakkot> | s/in React/in JSX/ |
23:37 | <devsnek> | what does a loop in jsx mean |
23:37 | <Bakkot> | https://stackoverflow.com/questions/22876978/loop-inside-react-jsx |
23:38 | <devsnek> | that's just people not understanding the basics of imperative programming |
23:38 | <TabAtkins> | That said, if I'm reducing like that, I already need to declare a var to hold the intermediate results, so I might as well just list that var's name *after* the loo pto get it returned |
23:39 | <rkirsling> | that seems pretty JSX-specific, in that it's conflating things that wouldn't be conflated in a templating language |
23:39 | <TabAtkins> | In `let sum = do{let sum = 0; for(const x of vals) sum += x; sum};`, the `; sum;` at the end isn't killing me |
23:39 | <devsnek> | anyway if we make sure completions in the language are clean |
23:39 | <Bakkot> | I think a lot of people will be surprised that `do { for (i = 0; i < 10; ++i) i }` does not give you an array with [0, ..., 9] |
23:39 | <devsnek> | and people generally don't do weird stuff |
23:39 | <devsnek> | i don't think it will be a problem |
23:40 | <devsnek> | like there's always that one person who writes jsfuck |
23:40 | <TabAtkins> | Looking back at the hoc docs, they give an example of using multiple hocs at once as: |
23:40 | <Bakkot> | https://github.com/tc39/proposal-do-expressions/issues/14 is the issue for loops in particular |
23:40 | <TabAtkins> | `withRouter(connect(commentSelector)(WrappedComponent))` |
23:40 | <devsnek> | Bakkot: let them be surprised, they might learn how imperative programming works |
23:40 | <TabAtkins> | and already I'm having trouble reading that. |
23:40 | <Bakkot> | devsnek :( |
23:41 | <Bakkot> | devsnek that seems like... not a good design principle |
23:41 | <TabAtkins> | `WrappedComponent |> connect(commentSelector)(#) |> withRoute(#)` |
23:41 | <devsnek> | i mean |
23:41 | <devsnek> | i don't think do expressions exacerbate that misunderstanding |
23:41 | <Bakkot> | devsnek: specifically, there is nothing about "imperative programming" which means that has to return a single value and not an array |
23:41 | <rkirsling> | Bakkot: I do disagree with the OP there though |
23:41 | <Bakkot> | we could define do expressions in a way which makes that return an array |
23:41 | <ljharb> | it still doesn't make any sense to me that a loop has a completion value. |
23:41 | <devsnek> | you're doing something and not putting the value anywhere |
23:42 | <Bakkot> | devsnek I mean that is true for any do expression |
23:42 | <Bakkot> | the whole point of using completion values is that you are not putting the value anywhere |
23:42 | <devsnek> | like i get that its confusing for n=1 |
23:42 | <Bakkot> | it just gets picked up for you |
23:42 | <rkirsling> | as TabAtkins said, why wouldn't you expect it to be the reduce result? why would you expect it to build you a thing implicitly? |
23:42 | <ljharb> | devsnek: why would you have a loop with n < 2 |
23:42 | <ljharb> | devsnek: actually i find it confusing with n > 1 |
23:43 | <devsnek> | ljharb: i get that people are surprised about completions |
23:43 | <devsnek> | but if you evaluate it x times |
23:43 | <devsnek> | where x is not one |
23:43 | <rkirsling> | like, if there is no array, where would it come from |
23:43 | <ljharb> | rkirsling: the array thing i don't find reasonable |
23:43 | <devsnek> | i don't understand the logic of where all those values are doing |
23:43 | <devsnek> | going* |
23:43 | <ljharb> | (altho the for..of part kind of makes sense to me) |
23:43 | <devsnek> | to me it feels like someone copy pasted some code without understanding it |
23:44 | <ljharb> | isn't that stackoverflow's growth model |
23:44 | <devsnek> | it feels like the kind of question we get in discord.js support server |
23:44 | <Bakkot> | I don't think that "the completion value of a loop is an array holding all of the completion values of each step of the loop" is a totally unreasonable thing to think |
23:44 | <Bakkot> | it isn't, but like, there is no particular reason for it not to be |
23:45 | <devsnek> | yeah but at that point you have some idea of completion values |
23:45 | <Bakkot> | ... yes? |
23:45 | <Bakkot> | and? |
23:45 | <TabAtkins> | diy list comprehension, yo |
23:45 | <rkirsling> | insofar as users have no conception of completion values, sure lol |
23:45 | <devsnek> | the jsx thing has nothing to do with completions |
23:45 | <Bakkot> | ok pretend I never brought up JSX |
23:45 | <devsnek> | i'm down to talk about making the loop produce an array |
23:45 | <Bakkot> | ehh |
23:45 | <Bakkot> | I don't really think it should |
23:45 | <devsnek> | but i don't think anything here is beyond comprehension |
23:46 | <devsnek> | i also don't think it should |
23:46 | <rkirsling> | ^ pun intended? |
23:46 | <devsnek> | lol ross |
23:46 | <Bakkot> | my actual position is that we should not expose to users the completion value of a loop |
23:46 | <rkirsling> | I will concede that https://github.com/tc39/proposal-do-expressions/issues/14#issuecomment-359529937 gives me pause |
23:46 | <ljharb> | ^ +1 |
23:46 | <rkirsling> | yeah I think I can agree with that sentence too |
23:46 | <devsnek> | ok to rephrase |
23:46 | <devsnek> | i'm ok talking about what the completion value of a loop should be |
23:46 | <devsnek> | but i don't think a loop having a completion value is inherently a bad thing |
23:47 | <Bakkot> | right, and my position is the negation of that last sentence |
23:47 | <devsnek> | whether its undefined or an array or the last expression or whatever |
23:47 | <TabAtkins> | I am cool with all the non-obvious statement cases being defined as producing undefined. So long as if() and try/catch give me their final values, everything else can take a hike as far as i'm concerned |
23:48 | <ljharb> | TabAtkins: the if block? |
23:48 | <TabAtkins> | yeah |
23:48 | <ljharb> | TabAtkins: blocks having a completion value of their last statement's makes sense to me |
23:48 | <ljharb> | any blocks |
23:48 | <ljharb> | but not loop bodies |
23:48 | <Bakkot> | my position is, I would like us to only allow do expressions for which the completion value is going to be obvious - so, you should not be able to end a do expression in a loop (or a declaration, which is also weird) |
23:48 | <devsnek> | i agree with tabatkins |
23:48 | <ljharb> | sounds like we have a compromise point |
23:48 | <devsnek> | that being |
23:48 | <devsnek> | the loop is allowed to be the last item |
23:48 | <devsnek> | but it just always gives undefined |
23:49 | <ljharb> | oh. i don't think that was what tab said |
23:49 | <rkirsling> | wait whoa why is `do {} while (false)` different from `{}` |
23:49 | <TabAtkins> | (I'm also fine with "everything works exactly as if you just plugged it into eval()", fwiw.) |
23:49 | <ljharb> | rkirsling: because it's a loop and `{}` is not |
23:49 | <TabAtkins> | devsnek accurately summarized me, yeah |
23:49 | <rkirsling> | yes |
23:49 | <rkirsling> | that is my question |
23:49 | <Bakkot> | TabAtkins would you also be ok with, ending a do expression with a loop is a syntax error? |
23:49 | <devsnek> | that's part of the es2015 completion reform |
23:49 | <TabAtkins> | yeah |
23:49 | <devsnek> | Bakkot: i would be against that |
23:49 | <Bakkot> | rkirsling basically "things are sometimes empty, sometimes not, at runtime" was held to be confusing |
23:49 | <ljharb> | rkirsling: i'm saying that's why it's different. loops make 0, 1, or N completion values, blocks make 1. there's no ambiguity about blocks, there is with loops |
23:49 | <devsnek> | idk how strongly exactly |
23:49 | <Bakkot> | devsnek why? |
23:50 | <rkirsling> | ah |
23:50 | <rkirsling> | right, you might not even enter a loop body |
23:50 | <rkirsling> | that's a good point |
23:50 | <devsnek> | it feels wrong to limit things like that |
23:51 | <devsnek> | maybe i can be convinced |
23:51 | <devsnek> | i can't be convinced about control flow though, that's a must |
23:51 | <Bakkot> | devsnek fwiw this is the sort of limitation we could relax later |
23:51 | <Bakkot> | devsnek :( |
23:51 | <rkirsling> | it feels wrong but it's not unthinkable |
23:51 | <Bakkot> | devsnek control flow is also a thing which could be relaxed later |
23:51 | <devsnek> | nah my use case doesn't work without it |
23:51 | <Bakkot> | well |
23:51 | <Bakkot> | there are other use cases |
23:51 | <rkirsling> | loops would be puntable where non-loop block-based constructs aren't |
23:51 | <Bakkot> | sometimes we add things which do not meet your particular use case |
23:51 | <Bakkot> | that is ok |
23:51 | <devsnek> | i mean my motivation for possibly furthering the proposal |
23:52 | <Bakkot> | ah, fair |
23:52 | <devsnek> | is to allow control flow in expression positions |
23:52 | <Bakkot> | yeah |
23:52 | <ljharb> | that's something i feel pretty strongly shouldn't be allowed |
23:52 | <TabAtkins> | Given that the point of a do-expr is to *return a value*, I don't see a signfiicant difference between "ending with a loop returns undefined" and "ending with a loop is a syntax error". |
23:52 | <devsnek> | TabAtkins: yeah i see both sides |
23:52 | <rkirsling> | ^ I like this phrasing |
23:52 | <Bakkot> | ok I might try to present the minimal form of do expressions |
23:52 | <ljharb> | TabAtkins: it's also to have statements in expression position |
23:52 | <devsnek> | i just don't like limitations |
23:52 | <TabAtkins> | The latter will catch programming mistakes earlier; the former will allow me to spin a loop for side effects at expression context. |
23:52 | <ljharb> | TabAtkins: which doesn't require a value |
23:52 | <Bakkot> | which basically ban anything confusing or contentious |
23:53 | <devsnek> | i find your banning of contentious items contentious |
23:53 | <ljharb> | lol |
23:53 | <Bakkot> | ljharb what expression positions don't require a value? |
23:53 | <ljharb> | Bakkot: i mean, there must *be* a value - like undefined - but it doesn't have to have meaning |
23:53 | <TabAtkins> | yeah the whole point of expression context is "here comes a value, beep beep" |
23:53 | <Bakkot> | devsnek sure, but the alternative appears to be "do expressions stagnate forever", so... |
23:53 | <rkirsling> | combining "make everything an expression" and "for side effects" just hurt my head |
23:53 | <ljharb> | `void do { … }` is a totally fine way to run side effects in expression positions. |
23:53 | <rkirsling> | (or maybe it was my heart) |
23:53 | <ljharb> | where "fine" is that dog meme |
23:53 | <devsnek> | yeah like |
23:54 | <Bakkot> | ljharb I mean to say, when would that come up? I feel like "I am forced to be in expression position, but I don't care what the value is" is fairly rare |
23:54 | <devsnek> | its weird |
23:54 | <ljharb> | Bakkot: true |
23:54 | <devsnek> | but that's what i like about js |
23:54 | <ljharb> | Bakkot: but i think ending with a loop, and not a value, is equally rare |
23:54 | <ljharb> | Bakkot: or will be in an expression position, i mean |
23:54 | <Bakkot> | ljharb well, I think some people will expect it to get an array, is the thing |
23:54 | <Bakkot> | and try it, and get bit |
23:54 | <ljharb> | fair |
23:55 | <rkirsling> | I mean, I would say it's on par with the early error for ** |
23:55 | <devsnek> | what if the completion value of a loop is a string containing info for the mailing list |
23:55 | <ljharb> | … maybe still better than "last value" |
23:55 | <ljharb> | :-p |
23:56 | <rkirsling> | "congrats you're the 10,000th caller! what do you think this should do" |
23:56 | <TabAtkins> | "side effects in expression context, don't care about the value" can still be done with `do{ for(){...}; 0}` |
23:56 | <devsnek> | lol |
23:56 | <devsnek> | my main thing is thinking about how generated code can use do expressions |
23:56 | <devsnek> | that's my primary use case for them anyway |
23:56 | <ljharb> | TabAtkins: very true. |
23:57 | <ljharb> | devsnek: generated code can also generate boilerplate to capture whatever return value you want |
23:57 | <devsnek> | yeah but that's extraordinarily difficult |
23:57 | <rkirsling> | what sort of generated code |
23:57 | <Bakkot> | generated code has a lot of freedom to just do things in a different way |
23:57 | <devsnek> | like babel codemods |
23:57 | <Bakkot> | put an IIFE there, rewrite control flow, whatever |
23:57 | <devsnek> | i don't mean compiler output |
23:58 | <ljharb> | devsnek: how is it difficult? add `let completion;` to the top, and `completion =` in front of the last value in the loop body, and `completion;` after? |
23:58 | <Bakkot> | TabAtkins re the "reduce" thing, one other case is, you are searching for an item and want that item |
23:58 | <Bakkot> | in which case it's more awkward |
23:58 | <devsnek> | ljharb: i was talking more about control flow |
23:58 | <Bakkot> | this is the example in https://github.com/tc39/proposal-do-expressions/issues/34 |
23:58 | <Bakkot> | under "for loop" |
23:58 | <devsnek> | rewiring the values is pretty easy |
23:59 | <Bakkot> | rewiring the control flow is also pretty easy |
23:59 | <TabAtkins> | ah yeah, without a `break with` that rewrites the completion value, you gotta do the "declare a temp, for(), temp" thing |
23:59 | <devsnek> | yeah just fork regenerator |
23:59 | <Bakkot> | generators are way more powerful than the break-continue-return kind of control flow we're talking about here |