You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This does not require knowledge of the WASM crate's internals, only a high level knowledge of Python and JS objects.
A couple questions about adding a JsValue class to WASM:
When passing in a JS value into python, where should the line be between JsValue and builtin Python type? e.g. JS strings and numbers should be converted to their corresponding Python primitives, but what about arrays/lists? TypedArrays/bytes/bytearrays?
Should functions be JsValues too? Should JsValue have a __call__ method?
Should we have a __getattr__ method on JsValue?
Should we throw an error when a JsValue doesn't have an attribute, or just return None?
How should we transfer kwargs to JS functions? Right now it provides them as the this variable, but should we change that?
How should errors transfer/convert between the two languages?
Not all of these questions are necessarily hard to answer, but it's good to have them on the table.
Simple values have simple mappings to related python types (int | float) => number, null => None etc. The question of JS's more complex types is actually rather interesting and I think should be tackled on a case by case basis.
TypedArray's actually have an equivalent mapping in Python's standard library array. I think it might be worthwhile to explicitly map these types together as they seem to be mostly mirrors outside of JS supporting 8 byte 64bit Bigints alternatively reducing to bytearray in python might be more universal
in general, my position is that serialisation boundaries should be strict in at least one direction (namely Javascript into python's VM), allowing only a list of types with well-defined transformations to pass. moving out of python objects can be mapped rather transparently into JS Objects so we can be less strict with the types we don't have defined transformations for.
would there be any benefit to allowing function objects to pass through the wasm layer? I wouldn't think so unless there was a reliable way to pass them back in or call them from javascript in such a way that they keep their VM bindings intact.
this question confuses me. it implies that we can call functions across what I assumed would be a serialization boundary between JS and the WASM layer if not also between the WASM and python VM layer. or is this being asked form the python side for a __getattr__ for the properties of an object passed from JS? In either case, I feel like the ability to pass arbitrary objects between the two environments is a bad idea so a __getattr__ would be rendered moot.
so, I'm writing this as I read over the questions so forgive me if this seems confused compared to the rest of it. This last question about how to pass **kwargs can be passed into the Python VM and properly called. I wasn't even aware this could be done with WASM in general because I was under the impression there was a serialization layer between JS and the WASM VM. I'd need to see a concrete example but if a consistent call signature then binding them to a known name in the this object seems appropriate
Errors should probably be passed as JS Error's and either raised explicitly or returned in the tuple (result, err) pattern.
My conclusion: I feel I actually do need an understanding of at least some of the finer points how calls between the two environments work to properly participate. reading the current WASM code on the master branch can only do so much. perhaps this discussion can coincide with an effort to better document the WASM module?
Oh, my apologies, I guess I assumed some knowledge about the WASM crate. There are currently function bindings, both ways, between JS and Python. When you call a JS function from Python, it transforms the kwargs into a JS object and supplies it as the this argument. When you call a Python function from JS, it takes an object and an array, for kwargs and args respectively. (The two arguments are a limit of wasm-bindgen, a closure can only have so many arguments, no varargs.) The idea for a JsValue class would be so we wouldn't have to manually provide bindings for every possible JS object someone might want to interact with in Python, e.g. an Event someone might get from an event listener. So, the idea with __getattr__ was: Should we let someone access a property on a JS object like obj.prop or keep it to __getitem__ (obj['prop']).
Ah, thank you that clears up my confusion. in that case, my position is this:
types form JS should be mapped to Python types only when there is a clear, unambiguous mapping from one language to another, eg. Number to float (we're going to want to be careful about size there), etc. Otherwise, it should map to a JsValue class as suggested.
my opinion is that JsValue should store the properties of the JS object including its type (if that can be reliably determined) as _ attributes. ie. _props, _js_type
and a __getattr__ should be defined along the semantics of:
def__getattr__(self, attr):
ifattrinself._props:
returnself._props[attr]
raiseAttributeError("JsValue object has no property {}".format(attr))
a __getitem__ I feel should not be used for properties as it would break pythonicness
In the case of JS functions passed into python, I think these should be mapped as a JsValuCallable class which implements an appropriate __call__ function.
__call__ should be something along the lines of def __call__(self, *args, **kwargs): where *args is used to populate the positional args of the JS callable, and attaching **kwargs as properties of this in JS as you said WASM does seem the most reasonable option.
information from the JS function's prototype should be pulled into attributes of the JSValueCallable. name and length are the important ones, but any other information that is gleanable should be used.
a TypeError should be raised if there are too many or too few *args to __call__, I know technically javascript won't care because the extra will be lost, and the not passed ones will be undefined but I feel every effort should be made to make python feel pythonic even in the browser dealing with JS values.
going the other direction of python callables in js, it should probably be made as painless a possible so py_func_in_js(args: any[] [, kwargs: object]) works about as well as one could wish`
Id say that the best case is that Errors get converted with a proper stack trace. we may need to wait for wasm-bindgen#1173. Ideally, we would get an intelligible stack trace out of unwinding a python error caused by a Js exception and vise versa. any solution that allows that with no extra work for user code is the way to go.
a TypeError should be raised if there are too many or too few *args to call
The problem with that is that JS didn't used to have the concept of optional args (e.g. function(a = 1){}), so some old or backwards-compatible code might do it like function(a) { if (a === undefined) a = 1; }, which has the same functionality except for making func.length1 where it was 0 for ES6 default args. I think we should prioritize usability of a JS function with any kind of signature over making things more Pythonic, and perhaps consumers of a JS library could write wrapper libraries over more "low-level" JS libraries, á la CPython with _io vs io.
some old or backwards-compatible code might do it like function(a) { if (a === undefined) a = 1; }, which has the same functionality except for making func.length1 where it was 0 for ES6 default args.
darn, I completely forgot about default args with that suggestion. I feel there should at least be a warning injection when the arg number's don't match with perhaps an option to turn it off. but that's just me being idealistic.
I think we should prioritize usability of a JS function with any kind of signature over making things more Pythonic
At the same time, as you said, they do have a concept of optional args now, so why not enforce more "strict" code? (In fact, being idealistic, it could even be possible to generate both typescript and mypy type definitions to enforce both sides from developer's side.)
What exactly is the use case for a JsValue class? The point of JsValue iirc is to emulate dynamic typing in rust, so I'm failing to see how it even makes sense to directly expose it to python.
So that when we pass a JS value into Python, Python code can manipulate it without us needing to define a specific class, e.g. an Event from an EventListener.
The point of JsValue iirc is to emulate dynamic typing in rust
No, that's std::any::Any. JsValue is a representation of a JavaScript value that can be manipulated from Rust, and the same would go for a Python JsValue.
So that when we pass a JS value into Python, Python code can manipulate it without us needing to define a specific class, e.g. an Event from an EventListener.
Given that python primitives would map to js primitives, it seems like what you want is a wrapper around js_sys::Object, not JsValue.
No, that's std::any::Any. JsValue is a representation of a JavaScript value that can be manipulated from Rust, and the same would go for a Python JsValue.
I guess I phrased that poorly - I didn't mean emulate dynamic typing in general, but rather that exposing a value from a dynamically typed language like JS needs a type like JsValue to be usable from rust. Exposing that directly to Python seems nonsensical to me. What makes sense is to expose each variant (ideally using the js_sys wrappers).
Exposing that directly to Python seems nonsensical to me. What makes sense is to expose each variant (ideally using the js_sys wrappers).
The thing is that Python is also dynamically typed, so there's not really a big ergonomics difference between using a general JSObject and a hand-crafted wrapper to map to a js_sys type. I'm also sorta opposed to individual wrapper types because it seems like a lot of manual labor for what I see as not a ton of gain.
Activity
Ryex commentedon Apr 9, 2019
Here are my thoughts
Simple values have simple mappings to related python types
(int | float) => number,null => Noneetc. The question of JS's more complex types is actually rather interesting and I think should be tackled on a case by case basis.TypedArray's actually have an equivalent mapping in Python's standard libraryarray. I think it might be worthwhile to explicitly map these types together as they seem to be mostly mirrors outside of JS supporting 8 byte 64bit Bigints alternatively reducing tobytearrayin python might be more universalin general, my position is that serialisation boundaries should be strict in at least one direction (namely Javascript into python's VM), allowing only a list of types with well-defined transformations to pass. moving out of python objects can be mapped rather transparently into JS
Objectsso we can be less strict with the types we don't have defined transformations for.would there be any benefit to allowing function objects to pass through the wasm layer? I wouldn't think so unless there was a reliable way to pass them back in or call them from javascript in such a way that they keep their VM bindings intact.
this question confuses me. it implies that we can call functions across what I assumed would be a serialization boundary between JS and the WASM layer if not also between the WASM and python VM layer. or is this being asked form the python side for a
__getattr__for the properties of an object passed from JS? In either case, I feel like the ability to pass arbitrary objects between the two environments is a bad idea so a__getattr__would be rendered moot.so, I'm writing this as I read over the questions so forgive me if this seems confused compared to the rest of it. This last question about how to pass
**kwargscan be passed into the Python VM and properly called. I wasn't even aware this could be done with WASM in general because I was under the impression there was a serialization layer between JS and the WASM VM. I'd need to see a concrete example but if a consistent call signature then binding them to a known name in thethisobject seems appropriateErrors should probably be passed as JS
Error's and either raised explicitly or returned in the tuple(result, err)pattern.My conclusion: I feel I actually do need an understanding of at least some of the finer points how calls between the two environments work to properly participate. reading the current WASM code on the master branch can only do so much. perhaps this discussion can coincide with an effort to better document the WASM module?
coolreader18 commentedon Apr 9, 2019
Oh, my apologies, I guess I assumed some knowledge about the WASM crate. There are currently function bindings, both ways, between JS and Python. When you call a JS function from Python, it transforms the kwargs into a JS object and supplies it as the
thisargument. When you call a Python function from JS, it takes an object and an array, for kwargs and args respectively. (The two arguments are a limit ofwasm-bindgen, a closure can only have so many arguments, no varargs.) The idea for aJsValueclass would be so we wouldn't have to manually provide bindings for every possible JS object someone might want to interact with in Python, e.g. anEventsomeone might get from an event listener. So, the idea with__getattr__was: Should we let someone access a property on a JS object likeobj.propor keep it to__getitem__(obj['prop']).Ryex commentedon Apr 9, 2019
Ah, thank you that clears up my confusion. in that case, my position is this:
types form JS should be mapped to Python types only when there is a clear, unambiguous mapping from one language to another, eg.
Numbertofloat(we're going to want to be careful about size there), etc. Otherwise, it should map to aJsValueclass as suggested.JsValueshould store the properties of the JS object including its type (if that can be reliably determined) as_attributes. ie._props,_js_type__getattr__should be defined along the semantics of:__getitem__I feel should not be used for properties as it would break pythonicnessIn the case of JS functions passed into python, I think these should be mapped as a
JsValuCallableclass which implements an appropriate__call__function.__call__should be something along the lines ofdef __call__(self, *args, **kwargs):where*argsis used to populate the positional args of the JS callable, and attaching**kwargsas properties ofthisin JS as you said WASM does seem the most reasonable option.JSValueCallable.nameandlengthare the important ones, but any other information that is gleanable should be used.TypeErrorshould be raised if there are too many or too few*argsto__call__, I know technically javascript won't care because the extra will be lost, and the not passed ones will beundefinedbut I feel every effort should be made to make python feel pythonic even in the browser dealing with JS values.going the other direction of python callables in js, it should probably be made as painless a possible so
py_func_in_js(args: any[] [, kwargs: object])works about as well as one could wish`Id say that the best case is that Errors get converted with a proper stack trace. we may need to wait for wasm-bindgen#1173. Ideally, we would get an intelligible stack trace out of unwinding a python error caused by a Js exception and vise versa. any solution that allows that with no extra work for user code is the way to go.
coolreader18 commentedon Apr 9, 2019
The problem with that is that JS didn't used to have the concept of optional args (e.g.
function(a = 1){}), so some old or backwards-compatible code might do it likefunction(a) { if (a === undefined) a = 1; }, which has the same functionality except for makingfunc.length1where it was0for ES6 default args. I think we should prioritize usability of a JS function with any kind of signature over making things more Pythonic, and perhaps consumers of a JS library could write wrapper libraries over more "low-level" JS libraries, á la CPython with_iovsio.Ryex commentedon Apr 10, 2019
darn, I completely forgot about default args with that suggestion. I feel there should at least be a warning injection when the arg number's don't match with perhaps an option to turn it off. but that's just me being idealistic.
adrian17 commentedon Apr 10, 2019
At the same time, as you said, they do have a concept of optional args now, so why not enforce more "strict" code? (In fact, being idealistic, it could even be possible to generate both typescript and mypy type definitions to enforce both sides from developer's side.)
OddCoincidence commentedon Apr 11, 2019
What exactly is the use case for a
JsValueclass? The point ofJsValueiirc is to emulate dynamic typing in rust, so I'm failing to see how it even makes sense to directly expose it to python.coolreader18 commentedon Apr 11, 2019
So that when we pass a JS value into Python, Python code can manipulate it without us needing to define a specific class, e.g. an
Eventfrom anEventListener.No, that's
std::any::Any.JsValueis a representation of a JavaScript value that can be manipulated from Rust, and the same would go for a PythonJsValue.OddCoincidence commentedon Apr 20, 2019
Given that python primitives would map to js primitives, it seems like what you want is a wrapper around
js_sys::Object, notJsValue.I guess I phrased that poorly - I didn't mean emulate dynamic typing in general, but rather that exposing a value from a dynamically typed language like JS needs a type like
JsValueto be usable from rust. Exposing that directly to Python seems nonsensical to me. What makes sense is to expose each variant (ideally using thejs_syswrappers).coolreader18 commentedon Apr 20, 2019
The thing is that Python is also dynamically typed, so there's not really a big ergonomics difference between using a general
JSObjectand a hand-crafted wrapper to map to ajs_systype. I'm also sorta opposed to individual wrapper types because it seems like a lot of manual labor for what I see as not a ton of gain.coolreader18 commentedon Apr 20, 2019
I think for some things it would definitely make sense to make a custom wrapper type for, but just not everything.