brython-dev / brython

Brython (Browser Python) is an implementation of Python 3 running in the browser
BSD 3-Clause "New" or "Revised" License
6.39k stars 511 forks source link

null/undefined/None comparisons, and None Bry=>JS conversion. #2272

Open denis-migdal opened 1 year ago

denis-migdal commented 1 year ago

Hi,

In JS we have :

null == undefined // true
null === undefined // false

It means that null and undefined are the same values, but are "different". In Python, I assume that is is more or less equivalent to === :

The == operator compares the value or equality of two objects, whereas the Python is operator checks whether two variables point to the same object in memory. In the vast majority of cases, this means you should use the equality operators == and != , except when you're comparing to None .

If we compare None to anything, it will always return False in Python, except while comparing it to None itself.

Therefore I think that in Brython we should have :

null == undefined // True <- currently is False
null  is undefined // False

We could also assume that None has the same value as null and undefined, but is different. This would help solving the following issues :

JS functions returns undefined, while Python function returns None, which might cause issues :

x = non_returning_function();
if x is None or x is undefined:  // we can't know whether x is undefined or None, which could lead to some unexpected consequences.
      pass

instead we could have :

x = non_returning_function(); // we can't know if x is undefined or None.
if x == None: # we "hide" the JavaScript complexity
      print('undefined or null or None')
if x is None:   # If we really want to ensure the return value.
     print('ensuring we do have None')

The same, when accessing to some JS values, have null == None could be expected, and would be coherent as undefined == null.

I do not think such behavior should produce unexpected side effect as :

For conversions between Brython <=> Javascript :

/!\ None value given to JavaScript function. Convert it to undefined or null before giving it. To disable this error option... to raise exception instead option .... [THE STACK]

Indeed, giving a None to Javascript could be dangerous, e.g. if it expects undefined for a callback return value.

// imagine a JS library exposing the following function :
function doStuff(callback) {

       if( callback() ) { // if null/undefined => false, BUT if None => true !
             // do stuff
       }
}

Hence the need to warn the user. The behavior could be controlled through an option given to Brython (e.g. NONE_CONVERSION) :

Cordially,

denis-migdal commented 1 year ago

One way to fix the function return type in Brython => JS callbacks, is to build functions in a way that implicit or empty return, returns an *internal()** temporary special value : e.g. <ImplReturn>.

(*) This means that Brython users will never see or access this value. It'll be just used internally to track implicit returns.

Then :

Then, we could have None being implemented as null*, which would remove the need to warn the user when trying to give None to JS.

(*) It was almost the case before, but couldn't due to non-returning function having their return values converted to null.

This could be implemented in several ways :

I think solution (c) might be a good solution. The browser should be able to optimize the local variable, hence the only cost is accessing to the stored return value. I'd argue that the cost of it should be very little compared to the frame and parameters management made at each function calls.

PierreQuentel commented 1 year ago

In the commit referenced above I have implemented javascript.NULL == javascript.UNDEFINED (Javascript is an endless source of surprises...)

I agree that there should be no "magic" conversion between None, null and undefined. I have no clear opinion at the moment if we should warn when None is passed to JS - perhaps mark it as deprecated ? But can we exclude use cases when we really want to pass None to a JS function ?

For the rest of your comments, there is something I don't get: you seem to assume that the developer doesn't know whether non_returning_function is a Brython function or a Javascript function. I think in the contrary that he actually knows where the function comes from, and

Am I missing something ?

denis-migdal commented 1 year ago

In the commit referenced above I have implemented javascript.NULL == javascript.UNDEFINED (Javascript is an endless source of surprises...)

Yeah, JS is a very dirty language.

Wait until you see that {}+[] is 0 but []+{} is an object xD.

In JS, == is when object has the same value after conversions/castings, meaning, that e.g. "" == false is true. When === is for a more strict comparaison, without conversions/castings, e.g. "" === false is false.

For values (sémantiques de valeurs), === is true when they have the same value, while for objects (sémantiques d'entités) it is true when they are the same reference.

For the rest of your comments, there is something I don't get: you seem to assume that the developer doesn't know whether non_returning_function is a Brython function or a Javascript function. I think in the contrary that he actually knows where the function comes from

When you work with callbacks, you can't be sure of the origin of the callback, or controls what the called function will do with the callback.

For exemple, if we use a JS function (e.g. in a library) and give him a callback :

function foo( callback ) {

       if( callback() ) {
             console.log('returned true');       
       }
}
def callback()
     pass

If the callback returns None, the condition will be evaluated to true, when it should be evaluated to false for a non-returning function.

Proposition A.

As null and undefined doesn't exists in Python, why not "hiding" their existence to Brython users ?

Meaning that return None internally returns null and return internally returns undefined.

Then for more control, only and only when wanting more control with JS/Brython interactions we could provide :

But this shouldn't be used by Brython users except in the very specific cases of interacting with JS : in python, there is no null or undefined, only None. So Brython users, except when wanting explicit interactions with JS shouldn't even know they are 2 things. As <null> is <undefined> in Brython, javascript.NoneNull is javascript.NoneUndefined would also be true (because they are <null> and <undefined>.

The naming enables to insist that they are None.

Proposition B.

Keep 3 internal values :

BUT

The naming enables to insist that they are None.