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

Object-docs (consolidated) #117

Closed
wants to merge 41 commits into from
Closed

Conversation

jmagaram
Copy link
Contributor

@jmagaram jmagaram commented Mar 22, 2023

I'm not sure I did what you wanted but I merged all the object doc strings I did into this PR. It was a hassle getting them all merged. So I think if you accept this then all the others can be closed.

@jmagaram jmagaram changed the title Merge branch 'Object.get-documentation' into Object-docs Object-docs Mar 22, 2023
Copy link
Contributor

@glennsl glennsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the comments below, and as pointed out in the previous PRs, all the get and set functions are unsafe. It might be outside the scope of this PR to rename them to add the Unsafe suffix, but I think the docstrings should still clearly say that they're unsafe, and explain why.

src/Core__Object.res Show resolved Hide resolved

See [Object.assign on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) or [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign).
*/
external assignMany: ({..}, array<{..}>) => {..} = "Object.assign"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. And with the potential functions assign2, assign3 and such of course.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend removing it and not introducing assign2, assign3, etc. As you noted other places the developer can chain calls to assign and get the same result. But I'll do what you recommend. What should I do?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't do what I recommend, but I agree :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. Who decides this? We both agree to remove it. @zth need your input here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who actually makes the decisions, and how, is just one of many mysteries around ReScript that you'll have to accept. The project has a history of poor management, though it's getting better! But still quite mysterious, unfortunately.

But for this library I suppose it's ultimately whoever has commit access, which is definitely @zth, and I'm sure @cknitt as well.

src/Core__Object.res Outdated Show resolved Hide resolved
src/Core__Object.res Outdated Show resolved Hide resolved
@jmagaram
Copy link
Contributor Author

Could you explain why they are unsafe? The getters will fail if given null and undefined. Anything else? When will setters fail?

@jmagaram
Copy link
Contributor Author

Hmm. Actually I don't think you can call this with a null or undefined object. So I don't know when the getters fail.

@glennsl
Copy link
Contributor

glennsl commented Mar 23, 2023

Could you explain why they are unsafe?

Sure, sorry. If it was that obvious these probably wouldn't exist in this form anyway.

Here are examples where get and set should fail, but don't. Compared to properly typed access:

let obj = {"foo": 42}

let x: int = obj["foo"]
// let x: string = obj["foo"] // Error: This has type: int, Somewhere wanted: string
let x: option<string> = get(obj, "foo") // Should error, as above, because it's actually an int

// obj["foo"] = "bar" // Error: This has type: string, Somewhere wanted: int
set(obj, "foo", "bar") // Should error, as above, because it's actually an int
// let x: string = obj["foo"] // Error: This has type: int, Somewhere wanted: string
let x: int = obj["foo"] // Just to show that the type is still int, despite now containing a string

@jmagaram
Copy link
Contributor Author

In several places I have suggested the getter return type unknown but have gotten zero thoughtful responses to that suggestion. I think that solves some of the issues because you can't unsafely cast to whatever you want. I prototyped some of it in a PR around the Type module, where I attempted to fix some real bugs like the fact that after you classify something you get a Type.object type and NOT something you can actually use as input to this module. And when you classify somethings as a symbol you get a Type.symbol and not something you can use as input in this module. The main feedback I got there was "just write docstrings". I want this to be a more collaborative process. It can't all be about the docstrings while serious functionality and safety and naming-problems (like getSymbol) exist.

I agree the setter is a problem when setting a property on an object ReScript thinks it knows the type of. Hadn't thought of that. Do you really think this warrants the Unsafe suffix? Or maybe just a usage warning/note in the docs?

@jmagaram
Copy link
Contributor Author

I attempted to write the docs for Object.createWithProperties but don't understand it. It seems the second parameter must be a "property descriptor" like this. I can't get this to work when the first parameter is anything but Object.empty(). So I can't do something like start with an {"a":1} and add properties to it. Can someone write some sample code to show me how, or explain it? The Object.create seems to clone the first parameter. I Googled for a bit and gave up.

Object.createWithProperties(Object.empty(), {"b": {"value": 42, "writable": true}}),

@glennsl
Copy link
Contributor

glennsl commented Mar 23, 2023

In several places I have suggested the getter return type unknown but have gotten zero thoughtful responses to that suggestion. I think that solves some of the issues because you can't unsafely cast to whatever you want.

It also removes the convenience and reason for these existing. These functions should probably just be removed.

I agree the setter is a problem when setting a property on an object ReScript thinks it knows the type of. Hadn't thought of that. Do you really think this warrants the Unsafe suffix? Or maybe just a usage warning/note in the docs?

As I've said elsewhere, I think everything unsound should be marked as Unsafe, because they break the type system guarantees that we rely on so much. And if doing so breaks someone's code because they were unaware of it being unsafe, then good! Now they're at least aware and can do something about it!

@jmagaram
Copy link
Contributor Author

In several places I have suggested the getter return type unknown but have gotten zero thoughtful responses to that suggestion. I think that solves some of the issues because you can't unsafely cast to whatever you want.

It also removes the convenience and reason for these existing. These functions should probably just be removed.

If the functions are removed then I wasted a lot of time on this. I just started documenting the ArrayBuffer stuff and that is dangerous too. If it is a waste of time I want to know now. I think we should provide safe alternatives wherever we can and there are none in the Object module. I thought it was safe when I saw the option<'a> but I was wrong. And then we can provide any number of shoot-self-in-foot alternatives so convenience doesn't suffer. This is all nearly zero lines of code and the docs are simple.

obj->get("code")->Unknown.toString->Option.getWithDefault("nope!") // most safe, least convenient
obj->get("code")->Unknown.toString->Option.getExn // a little scary
obj->get("code")->Unknown.toStringUnsafe // very scary

obj->getString("code")->Option.getWithDefault("nope!")
obj->getStringUnsafe("code") // least safe, most convenient

The other place where I care about this is Error.t. I still can't get the "code" property out of a custom exception. I'd like to see a safe getter there.

Need some decisions here. At a minimum I like the idea of beefing up the Type/Unknown module and sprinkling safe getters around. Then one short trip to the Unknown module and you can get your data out. And then if unsafe getters are really critical we can put them directly on Object or Error.t.

I'm going to wait for @zth before marking everything as Unsafe.

@glennsl
Copy link
Contributor

glennsl commented Mar 24, 2023

You're forgetting that the language has built-in syntax for object access too:

let x: string = obj["code"]

And this is entirely safe for well-defined object types.

The other place where I care about this is Error.t. I still can't get the "code" property out of a custom exception. I'd like to see a safe getter there.

This isn't going to help you with that though. But I like the idea of having these kinds of accessors in the Type/Unknown module instead, so that they're available for all types, not just objects.

@jmagaram
Copy link
Contributor Author

I'm not forgetting anything. I'm fully aware this Object module is a kludge for weird interop scenarios. That's why my docs include language like "ReScript provides type-safe compile-time support for JavaScript objects that may be more convenient/safe. See ..."

@zth
Copy link
Collaborator

zth commented Jul 12, 2023

Reviewing and looking to merge this soon. Could this be rebased perhaps?

@zth
Copy link
Collaborator

zth commented Jul 24, 2023

@jmagaram closing this since it's merged (after a rebase) in f5ae1be (although I messed up the commit text some). Thanks for doing this one, large effort! Sorry for leaving it hanging for so long.

@zth zth closed this Jul 24, 2023
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

Successfully merging this pull request may close these issues.

3 participants