Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can I use? #379

Open
mikeal opened this issue Jun 16, 2023 · 25 comments
Open

Can I use? #379

mikeal opened this issue Jun 16, 2023 · 25 comments

Comments

@mikeal
Copy link

mikeal commented Jun 16, 2023

Been waiting for this for a long time, am pretty bottlenecked right now on how well I can write new cryptography in JavaScript until this lands in at least v8.

This doesn’t seem to be progressing at the pace far less meaningful language additions seem to make it in. Any thoughts on what is taking so long and anything I can do to help? Most of the beneficiaries of this are uninvolved in standards but if it helps to get more people involved I can do that.

@acutmore
Copy link
Collaborator

Hi @mikeal,

Would you be able to provide some details on how you are hoping to make use of R&T?

With the latest feedback we have received from the committee we will need to make changes to the design before next attempting to move forwards.

The more use-cases we have to draw from the better we can measure how well different designs meet different use-cases.

@mlanza
Copy link

mlanza commented Jun 19, 2023

Well, one use case is doing ClojureScript in JavaScript, as I've been doing for many years now. ClojureScript provides maps and vectors instead of objects and arrays. Apart from Immutable.js, JavaScript lacks these.

As you know, until records and tuples there were no value type counterparts to objects and arrays. Therefore, I've pretty much been using objects and arrays as if they were records and tuples. That is, as a rule of discipline, I write only pure functions against them. So Imagine using the ClojureScript paradigm in JavaScript but without maps and vectors.

When records and tuples finally arrive, I can finally cease this practice. I'm thrilled to know records and tuples are on the horizon! I too have been eagerly anticipating their arrival as this will only improve what I've been doing.

@acutmore
Copy link
Collaborator

Thanks for those details @mlanza!

When records and tuples finally arrive, I won't have to employ this practice anymore. I'll be able use records and tuples instead of objects and arrays as value types and allow objects and arrays to be treated as reference types again.

If Records and Tuples were still reference values (=== compares them by reference) but there was a static helper function RT.equal(a, b) (hypothetical name) would this still be useful for ClosureScript? I could imagine (== a b) compiling down to JavaScript that uses a mix of both === and and this built in static method to achieve full 'value type' semantics.

@mlanza
Copy link

mlanza commented Jul 16, 2023

In my library I define protocols (my own version of the proposal for first-class protocols). In it, I define an equivalence protocol so that I can do exactly what you suggest. In effect that protocol treats the reference types as value types. But I'm looking forward to doing this legitimately and not by working around the difference between reference and value types.

In my library I treat objects and arrays as value types using this technique in the large. I break out of using the library when I want to treat them as the reference types they actually are.

@errorx666
Copy link

errorx666 commented Sep 11, 2023

I want to use records and tuples as keys to Maps and Sets for their value comparison semantics.

If Records and Tuples were still reference values (=== compares them by reference) but there was a static helper function RT.equal(a, b)

For me this defeats the whole purpose.

@nmay231
Copy link

nmay231 commented Oct 4, 2023

I want to use records and tuples as keys to Maps and Sets for their value comparison semantics.

Same for me. Particularly for use in describing coordinates as pairs/triplets of numbers. I've resorted to using array tuples of numbers and using .toString() to get something comparable by value.

@acusti
Copy link
Contributor

acusti commented Oct 21, 2023

R&T is the most exciting proposed change to javascript i’ve even seen, and my excitement, though grown dormant from years of waiting, is easily reawakened. my use case, at a high level, is using javascript as a functional programming language. more specifically, i want to use it for:

  1. having immutable data structures with ergonomic non-mutating methods for updating them (e.g. const updatedList = list.push('new item')) and for being able to use const and know it means that the variable’s value won’t ever change (const inconstantValue = { x: 1 } vs const constantValue = #{ x: 1 }).
  2. for highly performant value equality semantics, e.g. for storing props in a library like react and being able to efficiently avoid unnecessary rendering, or for an immutable state library (e.g. redux) where subscriptions need not be called if the state’s value after an action is dispatched is unchanged. i would prefer this to be made available via ===, but i guess RT.equal(a, b) could be viable. but it will be a PITA to have equality checks that have to do:
const areEqual = (a instanceof Record || a instanceof Tuple) && (b instanceof Record || b instanceof Tuple) ? RT.equal(a, b) : a === b;

though i suppose this theoretical .equal() method, even if it’s a static method on the Record and/or Tuple standard built-in objects, would handle primitive values as well.

@neftaly
Copy link

neftaly commented Dec 25, 2023

I'd like to talk to workers without the structured clone overhead or sharedarraybuffer burden! I'd also like to do the CLJS interop things, in order to have access to clojure libraries.

@acutmore
Copy link
Collaborator

acutmore commented Jan 8, 2024

I'd like to talk to workers without the structured clone overhead or sharedarraybuffer burden

You may be interested in https://github.com/tc39/proposal-structs?tab=readme-ov-file#shared-structs

@matthew-dean
Copy link

Is someone / a group championing this proposal and moving it forward? I also feel like this is probably one of the more exciting proposals to come to JS in a long time (maybe biggest since modules?) and would "complete" the language in terms of a complete set of immutable values. This would be especially valuable in reactive libraries, like Vue, where the "reactiveness" becomes difficult and unpredictable depending on what type of value is passed to a property.

@acutmore
Copy link
Collaborator

Hi @matthew-dean, this proposal was discussed at the most recent TC39 meeting. The notes for that are available here: https://github.com/tc39/notes/blob/main/meetings/2024-04/april-09.md#discussing-new-directions-for-rt

This would be especially valuable in reactive libraries, like Vue, where the "reactiveness" becomes difficult and unpredictable depending on what type of value is passed to a property.

This sounds interesting, would you be able to provide an example?

@matthew-dean
Copy link

@acutmore

This sounds interesting, would you be able to provide an example?

My bad, I think this is more strictly a React issue, where props are passed as-is and components do shallow equality checks to see if props have changed. Vue actually wraps all values / props in proxies, and as a result, crawls objects deeply to recursively set proxies in order to detect changes (I think). That said, I feel like that system is not perfect and I had issues setting values when the values are objects, but I can't currently reproduce it. 🤷‍♂️

So, I think the use case for a Record would be in systems where values can be anything, and there's some sort of memoization or "change" detection between values.

@RobertAron
Copy link

I may be a bit out of the loop but I'd like to throw in my two cents.

I remember hearing about this proposal years ago. I think I was introduced via this blog post:

https://sebastienlorber.com/records-and-tuples-for-react

In the time since then, the react team has built a compiler to solve the problem this post outlines. From my understanding, this new compiler is checking to see if you are treating your variables in a way that matches tuples and records. If you are it is able to change your code and optimize it in such a way that your values hold equivalency.

To me - this seems like a huge work around to something that would largely be solved by this proposal! I imagine having it directly built into the language would also be way more optimal from both a DX perspective, and a performance perspective.

I imagine this use case is already well documented within this proposal.

Typescript also has a work around for these features via the as const syntax. Its also a half measure since it doesn't allow for the deep comparison features, and also is still prone to programmer errors.

I guess I'm just surprised there isn't more excitement around getting this proposal going! It seems like the JS community has been tip-toeing around the idea of immutable data structures for decades at this point.

Is the main thing holding this proposal back really more use cases? I'd love to see this proposal become a reality.

I'm not super familiar with the proposal process, and have huge respect for everyone working on this. I don't mean to come off as harsh. I'm just really excited for this feature, and a little surprised at the rate of progress.

@mlanza
Copy link

mlanza commented Sep 7, 2024

Oh, it's definitely needed/wanted. The most popular libraries are using immutable data and there's already a proposal for making signals a primitive. If that happens it'll only lend support to records/tuples because nothing sits inside a state container better than an immutable of some kind. Via this approach one provides a functional core in which effects are simulated.

ImmutableJS exists and continues to find an audience. Records/tuples would subsume a portion of that audience. It can't subsume the entire audience because, like Clojure, their maps and sets use the standard hashing strategy (everything provides a hash and a means of equality checking). Records/tuples do not. They can use only primitives as keys. Still, they would be useful for anyone building an app from a functional core, which is what I've been exclusively doing for the past decade.

In my library, I've found that for performance reasons one chooses the simplest data structure that works. For example, I recently modeled a game where I started with ImmutableJS and found hashing to be too expensive for hundreds of lookups per second. I dropped ImmutableJS structures and chose simpler one that didn't require hashing lookups and greatly improved the performance. This is a gap that records/tuples would fill.

@acutmore
Copy link
Collaborator

acutmore commented Sep 9, 2024

I remember hearing about this proposal years ago. I think I was introduced via this blog post:

https://sebastienlorber.com/records-and-tuples-for-react

In the time since then, the react team has built a compiler to solve the problem this post outlines. From my understanding, this new compiler is checking to see if you are treating your variables in a way that matches tuples and records. If you are it is able to change your code and optimize it in such a way that your values hold equivalency.

To me - this seems like a huge work around to something that would largely be solved by this proposal! I imagine having it directly built into the language would also be way more optimal from both a DX perspective, and a performance perspective.

The React compiler is able to handle cases that R&T can't. For example the React compiler can memoize functions. It also shifts some of the work to compile time instead of runtime.

Typescript also has a work around for these features via the as const syntax. It's also a half measure since it doesn't allow for the deep comparison features, and also is still prone to programmer errors.

as const is interesting because at a high level they seem similar. But one does not completely replace the other. as const is "free" with no runtime overhead and can be used on all literals. R&T have runtime overhead and can only be used with "plain JSON like data".

I guess I'm just surprised there isn't more excitement around getting this proposal going! It seems like the JS community has been tip-toeing around the idea of immutable data structures for decades at this point.

The committee have been discussing this idea since at least 2012. Excitement is there, but it's spread out over a decade.

Is the main thing holding this proposal back really more use cases? I'd love to see this proposal become a reality.

Use cases can be the most valuable way to input into a design. We can compare how the proposal changes both how the code is written and how it performs. They provide the evidence that the proposal solves real issues. They also can help us test if an alternative design would be more/similarly effective.

I'm not super familiar with the proposal process, and have huge respect for everyone working on this. I don't mean to come off as harsh. I'm just really excited for this feature, and a little surprised at the rate of progress.

The proposal hit a roadblock in the design, this has made meaningful progress difficult. Sometimes the most valuable thing to do in this situation is to wait, let the dust settle and see if a new direction opens up.

The "easiest" way to reduce the complexity of this proposal is to drop support for === equality. However that has been the one of the headline benefits, that it composes with the existing way equality works in the language. With the cost that the values are restrictive, and not like regular objects and arrays despite their visual similarities.

I have been wondering if a reduced scope for the proposal of only being considering equal when used in Map and Set would still provide the desired value for a significant number of users, or if === equality should still be pursued.

The other core change that could be re-considered is dropping support for -0, which is perhaps the longest issue thread (over 200 comments) of the proposal #65. Supporting -0 has nice semantics but adds implementation complexity and performance overhead.

@slorber
Copy link

slorber commented Sep 16, 2024

The React compiler is able to handle cases that R&T can't. For example the React compiler can memoize functions. It also shifts some of the work to compile time instead of runtime.

That's true, but Records and Tuples can also handle things that the React Compiler will never solve.

For example: fetching twice the exact same data (ie refreshing it). Even if the React Compiler optimizes the memoization of derived data, the initial "source" of derivation remains newly created objects.

https://sebastienlorber.com/records-and-tuples-for-react#:~:text=Fetching%20and%20re%2Dfetching

One advantage of Records and Tuples is the ability to "stabilize" the data at the edges of the program (when fetching, parsing search params, reading localStorage etc). If the source is unstable in the first place, derived data will also be unstable.

@acutmore
Copy link
Collaborator

acutmore commented Sep 16, 2024

Absolutely. Though I don't think external sources are necessarily the best use case for R&T as the access to these requests are usually already being centralised through one place which can be cached and de-duplicate identical responses.

One thing that R&T do very well at is when there are separate sources of the same type of data. React's memoization is local to the hook/component, whereas R&T have essentially a global cache. Though that is also where the performance trade off comes.

@iambumblehead
Copy link

Are there ways to add new entries to the Record behaviour here? https://www.npmjs.com/package/@bloomberg/record-tuple-polyfill

import { Record } from '@bloomberg/record-tuple-polyfill/lib/index.esm.js'

const rec1 = Record({})
const rec2 = rec1.set('key', 'value') // TypeError: rec1.set is not a function

It would be nice if a package exported interfaces resembling the proposed Record and Tuple behaviour without polyfills and without need of any transpilers, frameworks or other sources of churn.

@acutmore
Copy link
Collaborator

That package is a polyfill for the proposal so it will only contain functionality that is also part of the proposal.

The other limitation is that the current proposal says that records do not have a prototype so there isn't a place to have such a set method.

@iambumblehead
Copy link

thanks I see also that AddPropertyIntoRecordEntriesList is not defined at the polyfill

@Maxdamantus
Copy link

const rec2 = rec1.set('key', 'value') // TypeError: rec1.set is not a function

Presumably you'd want Record({ ...rec1, key: "value" }), analogous to #{ ...rec1, key: "value" } with the proposed syntax changes.

@iambumblehead
Copy link

iambumblehead commented Sep 19, 2024

const rec2 = rec1.set('key', 'value') // TypeError: rec1.set is not a function

Presumably you'd want Record({ ...rec1, key: "value" }), analogous to #{ ...rec1, key: "value" } with the proposed syntax changes.

I see, however, it would be less useful if a package interface required one to use spread as it is slow. The benchmark below demonstrates the slowness of spread as compared with immutable-js' map.set,


This benchmark can be used with node's official benchmark.js scripts The last number is the rate of operations measured in ops/sec (higher is better).

note: node's common.js benchmark file uses one or two commonjs-specific things and those must be updated before the benchmark will run successfully

immutable-vs-spread-benchmark.js
import common from '../node-v22.9.0/benchmark/common.js'
import { Map as ImmutableMap } from 'immutable'

const TYPESIMPLEIMMUTABLE = 'map set (simple), immutable'
const TYPESIMPLEJSSPREAD = 'map set (simple), js spread'

const bench = common.createBenchmark(main, {
  n: [1400],
  mod: 0,
  type: [
    TYPESIMPLEIMMUTABLE,
    TYPESIMPLEJSSPREAD
  ]
})

async function main(conf) {
  // save then return this obj to try bypassing any
  // runtime optimisation identifying this code as unused
  let obj = Math.random()

  const type = conf.type
  const mod = conf.mod

  if (type === TYPESIMPLEIMMUTABLE) {
    bench.start()
    obj = ImmutableMap()
    for (let i = conf.n; i--;) {
      const key = 'key' + (mod ? i % mod : i)
      const value = 'value' + i
      obj = obj.set(key, {key, value})
    }
    bench.end(conf.n)
  }

  if (type === TYPESIMPLEJSSPREAD) {
    bench.start()
    obj = {}
    for (let i = conf.n; i--;) {
      const key = 'key' + (mod ? i % mod : i)
      const value = 'value' + i
      obj = {...obj, [key]: {key, value}}
    }
    bench.end(conf.n)
  }

  return obj
}
type="map set (simple), immutable" mod=0 n=1400: 187,746.68573457768
type="map set (simple), js spread" mod=0 n=1400: 4,405.721938920839

@iambumblehead
Copy link

does the proposed interface assume object spread will be used each time a new record is created from an existing record?

@acutmore
Copy link
Collaborator

The proposal is already a large change to the language so anything that wasn't part of the core semantics has been left for a follow on proposal.

One idea was a with syntax: #1

There is also this: https://github.com/tc39/proposal-deep-path-properties-for-record

@iambumblehead
Copy link

acknowledged, thank you for the reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests