Live data from Hacker News

Proposal: JavaScript Structs

github.com

331–340 of 371 posts

Re: Proposal: JavaScript Structs

#331

Earlier quoted context omitted.

OpenJDK and .NET compilers run circles around Go one. It's not even close. The second you go beyond "straight-line" code where function body has limited amount of locals and does not make much calls, the difference becomes absolutely massive. Go also does not do any sort of "advanced" devirtualization that is bread and butter of both to cope with codebase complexity and inevitable introduction of abstractions. Hell,…

Yep, for expert driven projects, such as Go and C#, it is nearly always a case of "everything is a tradeoff". Another good article for comparing GC between Go and C# https://medium.com/servicetitan-engineering/go-vs-c-part-2-g...

Noting that the article's findings from 2018 need to be re-evaluated on up-to-date versions before deriving conclusions because in the last 6 years (and especially in the last 3 or so for .NET) the garbage collector implementations of both Go and .NET have evolved quite significantly. The sustained multi-core allocation throughput graph more or less holds but other numbers will differ significantly.

One of the major factors that play in Go's favour is the right attitude to architecting the libraries - the zero-copy slicing is much more at the forefront in Go than in .NET (technically incorrect but not in terms of how the average impl. looks like), and the flexible nature of C# combined with it being seen as "be glad we even support this Microsoft's Java" by many vendors lead to poor quality vendor libraries. This results in the experience where developers see Go applications be more efficient, not realizing that it's the massively worse quality implementation of a dependency their .NET solution has to deal with (there was a recent comparison video, where .NET was estimated to be slower, but the reality was that it wasn't .NET but the AWS SDK dependency and the benchmark author being most familiar with Go and making optimal choices with significant impact there like using DB connection pooling).

I'm often impressed by how much punishment GC and compiler can take, continuing to provide competitive performance despite massive amounts of data reallocations and abstraction bloat thrown at it by developers who don't want to even consider to approach C# in an idiomatic C# way (at the very least by listening to IDE suggestions and warnings). In some areas, I even recommend to look at community libraries first which are likely to provide far superior experience if documentation and brief code audit indicate that its authors care(tm) which is one of the most important metrics.

Re: Proposal: JavaScript Structs

#332

Earlier quoted context omitted.

Been using .net since 2.0 and nah C# has jumped the shark. Primary constructors are a very poorly designed feature that for some reason was added in the last version. The new-ish yearly release cycle I think is mostly to blame, they feel like they need to add some headline features every year but the team also, maybe due to org-chart politics, seems to not really able to make deep runtime level changes that are neede…

> Primary constructors are a very poorly designed feature that for some reason was added in the last version. I upgraded to .NET 8 recently and I love primary constructors. I don't use them everywhere but they are great for dependency injection or for small classes.

Beware the footguns!

https://mareks-082.medium.com/dark-side-of-the-primary-const...

Let's just say they could have done a much better job on it. It feels rushed and haphazard for such a mature language.

Re: Proposal: JavaScript Structs

#333
post #308

I don't understand the need for the ever-growing list of "enhancements" to JS. Take Class for example. Class is entirely unnecessary and, essentially, tries to turn JS into a class-oriented language from its core which is object-oriented. I never create classes. I always create factory functions which, when appropriate, can accept other objects for composition. And I don't use prototypes, because they are unnecessary…

I like JS's flexibility too, but I have to point out that your object-oriented JS code is compiled into C++ classes by the v8 optimizer! (Unless you change their structure, in which case it gives up (deoptimization).)

Does it compile something like this into a c++ class?

makeThing(options, usethistoo) { let foo = options.foo; let thistoo = usethistoo;

  return {
    functions...
  }
}

Re: Proposal: JavaScript Structs

#334

Earlier quoted context omitted.

> If you allow arbitrary values, what's the difference between a record and a frozen object? The behaviour of equality. Frozen objects are already considered to have unique identities, in that `Object.freeze({}) !== Object.freeze({})` even though both objects are otherwise indistinguishable. This behaviour can't be changed and it relates to the fact that `Object.freeze(a) === a`. > I thought that the whole point is t…

Thanks for the history! Reading through the issues, I agree with you that some of the motivations against objects in records seem pretty strange. Mostly they seem to be around existing JS-written 'membranes' (related to the SES stuff mentioned above?) getting confused by primitives-containing-objects, depending on which permutation of typeof checks they use. Out of curiosity, do you think that the Shadow Realms propo…

> Out of curiosity, do you think that the Shadow Realms proposal they refer to will ever go anywhere?

I haven't really been following the Shadow Realm proposal (I'm not part of TC39, so only familiar with certain proposals), but I don't think it should conflict with R/T.

If R/T values are allowed to be passed between realms, they should effectively be "transformed" such that eg, `f(#[v])` is equivalent to `f(#[f(v)])` (where `f` is the transformation that allows values to be passed between realms). For "deeply immutable" values (no object references), `f(v)` will simply return `v` (eg, `#[42]`, f(#[42])` and `f(#[f(42)])` are all the same) and a membrane should be able to trivially optimise this case.

From this comment[0] it sounds like `f({})` in the current Shadow Realm proposal will throw an error, so I'd expect that `f(#[{}])` would also throw an error.

As you were pointing out, I think the only real contention between R/T and realms is in existing JS implementations of membranes, particularly because they might use the following condition to detect if something is "deeply immutable":

  v === null || typeof v !== "object" && typeof v !== "function"
If `typeof #[{}] === "tuple"`, then their `f` function will pass that value through without handling the contained object value by throwing or by creating/finding a proxy.

If `typeof #[{}] === "object"`, it should be fine because `f(#[{}])` will either throw or create/find a proxy for the tuple. There might be some unexpected behaviour around equality of R/T values passed through the membrane, but this is pretty obscure and it should be fixed once the membrane library is updated to handle R/T values.

Personally, I'm still not 100% convinced that the assumptions made from the above condition are important enough to cause such a change to the proposal, but I don't see the value of `typeof #[]` as being a usability issue. Code that needs to check the types of things is a bit smelly to me, but in cases where you do need to check the type, `typeof v === "tuple"` and `Tuple.isTuple(v)` both seem usable to me, so just making `typeof #[] === "object"` should be fine and it solves this hypothetical issue. This is similar to array objects, which are also fundamentally special (`Object.create(Array.prototype)` is not an array object) and are detected using `Array.isArray(v)`.

> Otherwise, there's the argument that "x.y" syntax shan't be used to access a mutable object from an immutable record, but that just feels like the all-too-common motive of "we must ensure that users write morally-correct code (given our weird idiosyncratic idea of moral correctness), or otherwise make them pay the price for their sins".

Agreed, and I've pointed out[1] that even the current proposal doesn't address this, since unless you've done some defensive check on `x`, there's nothing stopping someone passing a mutable object for `x` instead of a record. If you do want to perform a dynamic[2] defensive check, perhaps you should be asking "is it deeply immutable?" or even checking its shape rather than "is it a record?".

[0] https://github.com/tc39/proposal-record-tuple/issues/390#iss...

[1] https://github.com/tc39/proposal-record-tuple/issues/292#iss...

[2] If you're using a type system like TypeScript, this check should happen statically, because you'll use a type that specifies that it's both a record and the types of the properties within it, so your type will encode whether or not it contains mutable objects

Re: Proposal: JavaScript Structs

#335

Earlier quoted context omitted.

> Primary constructors are a very poorly designed feature that for some reason was added in the last version. I upgraded to .NET 8 recently and I love primary constructors. I don't use them everywhere but they are great for dependency injection or for small classes.

Beware the footguns! https://mareks-082.medium.com/dark-side-of-the-primary-const... Let's just say they could have done a much better job on it. It feels rushed and haphazard for such a mature language.

It doesn't seem that bad -- the lack of readonly would be my only concern out of that article and one I didn't actually consider.

I think, as a feature, this is sort of the MVP. They could have done a better job of it by adding more to it (e.g. maybe allow the readonly modifier on the constructor properties). It's hard to imagine them being able to take anything away from primary constructors that would make it better.

Re: Proposal: JavaScript Structs

#336
post #236

Earlier quoted context omitted.

To be more precise, aligned to whatever size such that you can guarantee field writes that don't tear. Pointer-aligned is a safe bet. 4-byte aligned should be okay too on 64bit architectures if you use pointer compression like V8 does. What kind of types did you have in mind? Machine integers and "any" (i.e., a JS primitive or object)? And yes, in browsers this will be gated by cross-origin isolation.

If the memory layout is fixed and fields are untyped then every field must be at least 8 bytes to potentially hold a double precision floating point value. There would clearly be value in adding typing to restrict field values to 1 or 2 or 4 byte integers to allow packing those fields. But I can see that it would add complexity.

Only if your implementation holds doubles without boxing them. V8 boxes doubles, but JSC and SpiderMonkey do not.

Re: Proposal: JavaScript Structs

#337
post #214

Earlier quoted context omitted.

Otoh, I create classes, use prototypes and it’s natural and useful in many of my cases. In my dreams those who want to turn JS into c# or Java should just create a language they like and stop piling on to JS. We could even share this dream if browser vendors weren’t such whos the boss iam da boss when it comes to extensions and alternatives. So we have to live in a common denominator, which surprisingly isn’t as bad…

I wonder why it seems natural to you? I'm guessing JS wasn't your first language and you didn't learn the power of composition instead of classes.

Hello JS was my first language and I use classes because sometimes it seems like the obvious way to model things.

Looking through the source of Replicache, here are some classes we use:

- KVStore

- DAGStore

- Transaction

I mean ... I can of course model these w/o classes, but encapsulating the state and methods together feels right to me. Especially when there is private state that only the methods should manipulate.

We use composition all over the place and rarely use inheritance so I don't think it's just some deficiency of knowledge .

Pre JS classes, the js community emulated classes w/ the prototype chain and that's what I'd have done for these classes if real JS classes weren't available.

Re: Proposal: JavaScript Structs

#338

Earlier quoted context omitted.

I wonder why it seems natural to you? I'm guessing JS wasn't your first language and you didn't learn the power of composition instead of classes.

Hello JS was my first language and I use classes because sometimes it seems like the obvious way to model things. Looking through the source of Replicache, here are some classes we use: - KVStore - DAGStore - Transaction I mean ... I can of course model these w/o classes, but encapsulating the state and methods together feels right to me. Especially when there is private state that only the methods should manipulate.…

closures can encapsulate state and methods classes are syntactic sugar

emulating classes is, imo, exactly the problem

using factory functions which create and return an object, with variables passed to and created in the function, handles encapsulation

and there is no `this` to deal with

Re: Proposal: JavaScript Structs

#339
Happy to see this effort.

When applying ReactJS in webdev after doing all kinds of engineering in all kinds of (mostly typed) languages in many runtimes, I was so surprised that JS did not actually had a struct/record as seen in C/Pascal. Everything is a prototype that pretends its an object, but without types and pointers, and abstraction layers that added complexity to gain backwards compatibility.

Not even some object hack that many OO and compiled languages had. ES did not add it either, and my hopes where in WebAsm.

This proposal however seems like the actual plan that i’d like to use a lot.

A lot of the code complexity was to get simple guarantees for data quality. The alternative was to not care, either a feature or caveat of the used prototype model.

Re: Proposal: JavaScript Structs

#340

I don't understand the need for the ever-growing list of "enhancements" to JS. Take Class for example. Class is entirely unnecessary and, essentially, tries to turn JS into a class-oriented language from its core which is object-oriented. I never create classes. I always create factory functions which, when appropriate, can accept other objects for composition. And I don't use prototypes, because they are unnecessary…

Classes generally point you towards writing more performant code. Factory functions allow you to achieve the same performance, you just have to be a bit more careful not to cause a depot :)

For example,

1. field declarations [1] make sure that the fields are always initialized in the same order. That way most of your functions end up monomorphic, instead of being polymorphic [2]

2. Method declarations are also (almost) free, since you only pay for them once, during class initialization.

You also get a few other niceties such as private properties. You can emulate private properties with closures in factory functions but V8 has a hard time optimizing, unfortunately.

---

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

[2]: https://www.builder.io/blog/monomorphic-javascript

Post reply on HN