stdlib-js / stdlib

✨ Standard library for JavaScript and Node.js. ✨
https://stdlib.io
Apache License 2.0
4.51k stars 494 forks source link

[RFC]: supporting `valueOf` in zero-dimensional ndarrays for scalar-like behavior #863

Open kgryte opened 1 year ago

kgryte commented 1 year ago

Description

This RFC proposes supporting valueOf() for primitive coercion of zero-dimensional ndarrays. Adding primitive type coercion would allow zero-dimensional ndarrays to exhibit scalar-like behavior in common unary and binary operations, such as addition, subtraction, etc.

A common design principle among ndarray APIs is (and will be) returning ndarrays, even when a scalar might be expected (e.g., computing the sum over a one-dimensional ndarray). This follows principles set forth in the Data APIs Standard, as consistently returning ndarrays is more conducive for whole-graph optimization, retaining dtype information, and ensuring that data can remain on device (e.g., GPU/TPU), thus avoiding unnecessary device synchronization.

Without valueOf() support, zero-dimensional ndarrays exhibit the following behavior:

In [1]: var x = ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' );

In [2]: +x
Out[2]: NaN

In [3]: 3 + x
Out[3]: "3ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )"

In [4]: x + 3
Out[4]: "ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )3"

In [5]: x + x
Out[5]: "ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )"

In [6]: Number( x )
Out[6]: NaN

In [7]: new Date( x )
Out[7]: Invalid Date

In [8]: 3.14 == x
Out[8]: false

In [9]: typeof x
Out[9]: 'object'

In [10]: x + 'foo'
Out[10]: "ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )foo"

In [11]: 'foo' + x
Out[11]: "foondarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )"

By adding valueOf() behavior, this RFC proposes the following behavior:

In [15]: x.valueOf = function() { return x.get(); };

In [16]: +x
Out[16]: 3.14

In [17]: 3 + x
Out[17]: 6.140000000000001

In [18]: x + 3
Out[18]: 6.140000000000001

In [19]: x + x
Out[19]: 6.28

In [20]: Number( x )
Out[20]: 3.14

In [21]: new Date( x )
Out[21]: 1970-01-01T00:00:00.003Z

In [22]: 3.14 == x
Out[22]: true

In [23]: x == 3.14
Out[23]: true

In [24]: x === 3.14
Out[24]: false

In [25]: 3.14 === x
Out[25]: false

In [26]: typeof x
Out[26]: 'object'

In [27]: x + 'foo'
Out[27]: '3.14foo'

In [28]: 'foo' + x
Out[28]: 'foo3.14'

In [29]: String( x )
Out[29]: "ndarray( 'generic', [ 3.14 ], [], [ 0 ], 0, 'row-major' )"

Making this change will allow zero-dimensional to (mostly) behave like their scalar equivalents. The exceptions are as follows:

Conclusion

In short, this proposal should strike a reasonable balance between allowing zero-dimensional ndarrays to be scalar-like in most cases and retaining ndarray behavior when operating on values across various ndarray APIs. There are subtle differences which do require vigilance in ensuring that one is explicit in terms of value type expectations, which is especially important in ambiguous contexts where a value may only be "scalar-like".

Related Issues

None.

Questions

Other

Checklist

rafiya2003 commented 1 year ago

can you please assign me this?

kgryte commented 1 year ago

@Planeshifter Have you had the opportunity to review this RFC?

Planeshifter commented 1 year ago

The proposal seems well-considered to me, with the exceptions being reasonable to retain ndarray behavior where expected.

Three suggestions we discussed: