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

with syntax #1

Closed
littledan opened this issue Apr 4, 2019 · 32 comments
Closed

with syntax #1

littledan opened this issue Apr 4, 2019 · 32 comments

Comments

@littledan
Copy link
Member

I was picturing that the syntax would be obj with .prop = value, and I see the syntax here as obj with prop = value. To me, seeing prop bare like that makes me think of a lexically scoped variable. Are you sure about omitting the .?

@littledan
Copy link
Member Author

I'm also wondering, how did you want the with syntax to work with examples like @const [] with push(1) with push(2)? How should this work on user-defined classes? I was picturing that with would mostly be used for deep paths.

As a separate issue, if the syntax a with b = c with d = e is used, there's a syntactic ambiguity about whether that means a (with b = c) (with d = e) or a (with b = c (with d = e)). For this reason, I was picturing that chaining would be used like a with b = c, d = e.

@littledan
Copy link
Member Author

About prototype chain changes: I don't understand why you wouldn't just put the method on the instance. And I don't understand the semantics either: __proto__ is a getter/setter pair; how do you want with to interact with this? I was picturing that with would throw a TypeError on things that are not const.

@rricard
Copy link
Member

rricard commented Apr 4, 2019

I agree with you. I’d like to put the dot. Phil had some strong opinions against it

@rricard
Copy link
Member

rricard commented Apr 4, 2019

__proto__ is a getter/setter pair;

TIL, I will probably make a lot of those mistakes looking forward, thank you for looking out for those issues

@rickbutton
Copy link
Member

+1 for a with b = c, d = e

I'm not sure how I feel about the . (a with .b = c, .d = e), because it feels very cumbersome (specifically, the space before the dot feels weird to me, and is unlike every other language that addresses members of objects in that way), but I can see how someone might interpret the dot-less version as introducing a new variable in scope.

how about:

const obj = @const { a: 1, b: 2 };
const obj2 = obj with.a = 1,
                 with.b = 2;

you end up specifying with twice, but the space-dot weirdness is gone, and the comma removes the syntax ambiguity.

@rricard
Copy link
Member

rricard commented Apr 12, 2019

Interesting... I'd say we might need a bit more external input here but otherwise this is a completely reasonable thing to consider

@zenflow
Copy link

zenflow commented Jun 6, 2019

Shouldn't this feature be made as a separate proposal, since it is orthogonal to the const/immutable value types feature ? It would be (or should be ) applicable to normal mutable value types too, wouldn't it?

I see huge value in the const/immutable value types, but I am unsure of the value of the with feature, given the complexity of it, and the fact that we have (or should have) other ways to achieve the same thing..

Without the with feature, we can (or should be able to) still use object & array spread to achieve the same thing:

Example:

Rather than this:

const map1 = const {
    a: 1,
    b: 2,
    c: 3,
};

const map2 = map1 with .b = 5;

... we can do this:

const map1 = const {
    a: 1,
    b: 2,
    c: 3,
};

const map2 = const {...map1, b: 5};

I think object spread way would be preferred because it's the familiar way of doing things, and in the above example it's every bit as elegant as the with way.

Updating nested values with object/array spread may be slightly less elegant, but I don't think the comparative elegance of using with outweighs the downside of inventing a totally new way of doing this.

Anyways, shouldn't that be debated and worked on in a different proposal?

@rricard
Copy link
Member

rricard commented Jun 6, 2019

So it's implicit and I should definitely clarify that but the spread syntax is supposed to have the effect you're describing so I'm probably going to steal your example there!

Also I think I'm gonna start phrasing the proposal so the core of the proposal is highlighted and we can have additional nice to have points, I think the with is definitely nice to have even if spreads are widely accepted they have a slightly different semantic and it should also be receivable.

@tolmasky
Copy link

tolmasky commented Jun 6, 2019

In my experience immutable programming often involves deep paths, and it would be nice to optimize this experience. By optimize I mean two things:

  1. Make it easy to write and understand
  2. Expose it in a way that can be treated as data.

For example, if [] allowed a list of items:

{ ...object, [key1, key2, key3]: value }

Then it would be easy to do, and more importantly, allows you to easily make the keyPath a variable:

{ ...object, [...keyPath]: value }

Currently, it is trivial to have one-level access be abstracted out into a variable, but multi-level access necessarily requires either manual syntax construction or a reduce. With the above, keys and keyPaths would be equally treatable as data.

@tolmasky
Copy link

tolmasky commented Jun 6, 2019

One additional thought, if we are using spread syntax instead of with objects, we could potentially do the same with arrays. It is already the case that push becomes simpler than with the with syntax (as shown in this previous comment):

[...array, 5] /* vs. */ array with push(5)
[5, ...array] /* vs. */ array with enqueue(5)

But we could do something similar for insertion as well:

[...array, [1] = 7] /* vs. */ array with [1] = 7

@rricard
Copy link
Member

rricard commented Jun 6, 2019

That's an interesting approach, and it could be a proposal on its own indeed... Let's try to collect feedback on this one

@tech6hutch
Copy link

I'm not sure how I feel about the . (a with .b = c, .d = e), because it feels very cumbersome (specifically, the space before the dot feels weird to me, and is unlike every other language that addresses members of objects in that way)

@rickbutton Not quite true, actually. Perl allows .access on the topic variable, without being prefixed with $_.

@tolmasky
Copy link

tolmasky commented Jun 7, 2019

Perhaps not an essential reason, but "with" already has a bad wrap in JavaScript. Googling "JavaScript with" is going to bring up a lot of confusing "Don't ever use this feature!", and "with puts v8 into a slow path!" type articles.

Separately I find it hard to parse the parenthesis-less version in a function call (vs. the commas delimiting arguments).

@pinyotensor
Copy link

With the new changes in the syntax (i.e. #{a: 1}), I think it makes much more sense to keep the spread syntax, because it would now be unambiguous:

const x = #{a: 1, b: 2}

// Const update
#{...x, a: 5} === #{a: 5, b: 2}

// Normal spread
{...x, a: 5} !== #{a: 5, b: 2}
deepEqual({...x, a: 5}, {a: 5, b: 2}) === true

@rricard
Copy link
Member

rricard commented Jun 14, 2019

Spread and with can coexist, they're not mutually exclusive.

@davidsharp
Copy link

davidsharp commented Jun 17, 2019

I'm not sure I'm entirely sold on the proposal of this with keyword, however, I do find it problematic that it clashes with the existing with statement.

@TehShrike
Copy link
Contributor

Must with be a part of the initial proposal? It seems like such a more complicated thing than the rest of the proposal.

I don't believe this proposal needs the with syntax to be viable. I also doubt that the proposed with syntax needs to be specific to immutable data structures – someObject with .b=5 would be handy as sugar for Object.assign({}, someObject, { b: 5 })

I would prefer to see this proposal advance on its own, and the with syntax be spun out into its own proposal.

@dead-claudia
Copy link
Contributor

Related: #19

@zenflow
Copy link

zenflow commented Jun 27, 2019

I would prefer to see this proposal advance on its own, and the with syntax be spun out into its own proposal.

Me too. It is really diluting the usefulness of the documentation to show how the const/immutable values feature works.

I would prefer it to be completely removed from this proposal, but if not, could we at least separate it out of the initial examples and try to focus on the primary feature (const/immutable values) there? @rricard

@zenflow
Copy link

zenflow commented Jun 27, 2019

btw, I don't really like how this feature invents totally new syntax and semantics.

Would it be possible to build on the existing syntax and semantics of object literals?

// instead of this:
someObject with .b = 5

// this is already possible:
{...someObject, b: 5}

// for deep updates, instead of this:
someObject with .c.d[x] = 4

// this would be more beautiful:
{...someObject, c.d[x]: 4}

@tolmasky
Copy link

@zenflow I think the keyPath syntax I suggested here accomplishes what you want, while also allowing you to have computed keyPaths (to match the existing ability to have computed keys).

@hax
Copy link
Member

hax commented Jul 19, 2019

It seems the key benefit of with clause is providing the deep path access (like .a.b[10] = value) and imperative-like operation (e.g. push/pop). But we may be not sure how useful is it and the syntax of with is a bit controversial.

I wonder if it's possible to start with a much common way to achieve the similar functionality:

import {update} from 'std:const-utils'

let foo = #[{a: 1}, {a: 2}]
foo = update(foo, v => {
  // v is a special proxy-like object to receive the operations
  v[0].a = 2
  v[1].a++
  v.push(#{a: 4})
})
assert(foo === #[{a: 2}, {a: 3}, {a: 4}])

Of coz with clause might has better potential performance optimization, but a normal function based solution is flexible and we can learn how programmers leverage it, and decide later how to design a syntax.

@mheiber
Copy link
Contributor

mheiber commented Aug 14, 2019

@hax your proposal sounds like #19. Would that be a good place to continue the discussion of non-with syntax?

@thysultan
Copy link

An alternative could be to use a destructuring-like syntax:

// objects
const a = obj pick {b: 5}
// arrays
const b = obj pick [0, , 2]
// nested
const c = obj pick {b: pick [0]}

@davidsharp
Copy link

davidsharp commented Aug 14, 2019

If we feel the need for a keyword (not that I think it should be part of the proposal), I think where could be a readable choice which doesn't clash with existing with:

const newRecord = myRecord where prop = 'foobar'
// or
const newRecord = myRecord where {prop: 'foobar'}

@rricard
Copy link
Member

rricard commented Aug 14, 2019

Ok, I think we derived a lot from the original issue opened by dan and we addressed things since then. We're not considering other keywords that are not in the reserved keywords list. So things like pick and where will not work. If you want to propose an another way of doing things, please do it in a different issue like #19, otherwise I'm just gonna close this one as the discussion becomes hard to follow.

@rricard rricard closed this as completed Aug 14, 2019
@TehShrike
Copy link
Contributor

Would it be appropriate for me to open a separate issue for "should with even be a part of this proposal"?

I don't believe I've seen any justification for including it in this proposal.

@borkdude
Copy link

borkdude commented Dec 7, 2023

I'm not exactly sure where I should ask this question. Perhaps Github Discussions would be a better place if that was enabled for this repo, but here it goes:

How will #{...a, b: 2} be implemented? I hope this doesn't do a full clone of the original a record, but uses some sort of structural sharing for efficiency, like in languages like Clojure, Scala, Haskell, etc?

@ljharb
Copy link
Member

ljharb commented Dec 7, 2023

The spec wouldn't define that; it'd be up to each implementation.

@borkdude
Copy link

borkdude commented Dec 7, 2023

Right. Just using the ... syntax doesn't imply cloning, it's up to each implementation how to implement spread, is that a good assumption? Is there an API for what spread does? Like #{a: 1}.with("a",2)?

@borkdude
Copy link

I have another question:

How would one distinguish between reference-equality and value-equality?

@ljharb
Copy link
Member

ljharb commented Dec 10, 2023

As primitives, they wouldn’t ever have reference equality/identity.

@acutmore acutmore mentioned this issue Sep 20, 2024
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