8000 util: add util.types.isBoxedPrimitive by BridgeAR · Pull Request #22620 · nodejs/node · GitHub
  • [go: up one dir, main page]

    Skip to content

    Conversation

    @BridgeAR
    Copy link
    Member

    Checking all boxed primitives individually requires to cross the C++
    barrier multiple times besides being more complicated than just a
    single check.

    Checklist
    • make -j4 test (UNIX), or vcbuild test (Windows) passes
    • tests and/or benchmarks are included
    • documentation is changed or added
    • commit message follows commit guidelines

    Checking all boxed primitives individually requires to cross the C++
    barrier multiple times besides being more complicated than just a
    single check.
    @BridgeAR BridgeAR requested a review from addaleax August 31, 2018 13:41
    @nodejs-github-bot
    Copy link
    Collaborator

    @nodejs-github-bot nodejs-github-bot added the c++ Issues and PRs that require attention from people who are familiar with C++. label Aug 31, 2018
    @addaleax addaleax added util Issues and PRs related to the built-in util module. semver-minor PRs that contain new features and should be released in the next minor version. labels Aug 31, 2018
    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Aug 31, 2018

    @BridgeAR BridgeAR added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Aug 31, 2018
    }
    }

    // Check boxed primitives.
    Copy link
    Member

    Choose a reason for hiding this comment

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

    Maybe add some negative results to the test?

    Copy link
    Member Author

    Choose a reason for hiding this comment

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

    This seems somewhat redundant to me, since this is just a helper combined out of different helpers that are already tested against all other types.
    But I'll add negative results as well if you feel strongly about it!

    Copy link
    Member

    Choose a reason for hiding this comment

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

    I think it makes sense to test - but don't feel strongly about it.

    @targos
    Copy link
    Member
    targos commented Sep 1, 2018

    Maybe name it isPrimitiveObject? I don't see the term "boxed" being used in the ecmascript specification or on MDN.

    Edit: It's more consistent with the names of the individual methods (there is no isBoxedBoolean)

    @jdalton
    Copy link
    Member
    jdalton commented Sep 1, 2018

    @targos You'll sometimes see boxed, unboxed, and auto-boxing come up in conversations. It's used to describe what happens when you access a property on a primitive value. For example:

    "a".trim // auto-boxes primitive "a" to access the property "trim"
    "a".a = 1 // box primitive for property assignment then unbox after
    "a".a // undefined because the property was assigned to the boxed value which was discarded

    MDN mentions boxing here , here, and here.

    That said, isPrimitiveObject is more precise so 👍

    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 3, 2018

    isPrimitiveObject sounds somewhat wrong to me. Turning it around to isObjectPrimitive seems somewhat better but still not awesome (isPrimitiveObject reminds me of an object that is primitive and I would not know what that should be. It somewhat reminds me of an plain object). However, I can not thin 8000 k of anything else as alternative and it does align with the other is...Object functions. Anyone else with more suggestions / reasons for either name?

    @addaleax
    Copy link
    Member
    addaleax commented Sep 3, 2018

    Turning it around to isObjectPrimitive seems somewhat better but still not awesome

    That would imply that we’re checking for “object primitives”, i.e. primitives which have something to do with objects. The values that pass the tests are definitely not primitives, though.

    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 3, 2018

    @addaleax

    primitives which have something to do with objects

    It does seems somewhat more intuitive for me (even if it's not really a primitive it is why we check for it: we want to know if it has something to do with a primitive by encapsulating one). I never heard of an "primitive object" and my head just can't get used to that name.

    @addaleax
    Copy link
    Member
    addaleax commented Sep 3, 2018

    I never heard of an "primitive object" and my head just can't get used to that name.

    I agree that it sounds odd, but I think it’s more accurate than “object primitive”. Tbh, I think isBoxedObject or isBoxedPrimitive both are okay, even if the spec doesn’t talk about boxing with that term.

    @targos
    Copy link
    Member
    targos commented Sep 3, 2018

    (it was just a suggestion, I'm not blocking anything. Just wanted to make sure alternative names were considered)

    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 3, 2018

    I am fine with either isBoxedPrimitive or isBoxedObject while having a preference for the former.

    Copy link
    Member
    @devsnek devsnek left a comment

    Choose a reason for hiding this comment

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

    isBoxedPrimitive sounds good to me. :shipit:

    I would also be okay with isObjectWithPrototypeThatIsOneOfThePrimitivePrototypeIntrinsics 😄

    @jdalton
    Copy link
    Member
    jdalton commented Sep 3, 2018

    I dig isPrimitiveObject because, as mentioned before, it fits with the existing naming convention of isXyzObject, like isBooleanObject and isNumberObject, by replacing Boolean or Number with Primitive. While more letters, Primitive for me is easier to grok at a glance than Boxed too.

    Update:

    I should make it clear I'm OK with the existing isBoxedPrimitive too
    (isPrimitiveObject is a preference for me is all).

    @addaleax
    Copy link
    Member
    addaleax commented Sep 3, 2018

    Totally scientific research: https://twitter.com/addaleax/status/1036696939365052418

    @ljharb
    Copy link
    Member
    ljharb commented Sep 3, 2018

    It's a boxed primitive imo; a "primitive object" doesn't make sense because the two words are mutually exclusive. It's also not a boxed object, it's an object formed by boxing a primitive into an object.

    (fwiw this is already polyfillable in pure JS; see https://twitter.com/ljharb/status/1036707165002559488)

    @jdalton
    < 628C summary data-view-component="true" class="timeline-comment-action Link--secondary Button--link Button--medium Button"> Copy link
    Member
    jdalton commented Sep 3, 2018

    @ljharb

    (fwiw this is already polyfillable in pure JS; see

    Yep, and we have util.types.isNumberObject and friends too. The addition in this PR, is part of exposing helpers used to make inspect perf better (it's handy for inspect and handy for those in the ecosystem too). See #22503.

    @ljharb
    Copy link
    Member
    ljharb commented Sep 3, 2018

    @jdalton to clarify; that it's already polyfillable is why this is safe to expose directly as a faster and much more friendly helper :-) i'm super on board.

    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 3, 2018

    The CI https://ci.nodejs.org/job/node-test-commit/21107/ is green by the way.

    @TimothyGu
    Copy link
    Member
    TimothyGu commented Sep 3, 2018

    Would it be worthwhile to instead implement this in JavaScript? It seems like it's possible to do so, and it's usually faster than an implementation that goes through C++.

    @ljharb
    Copy link
    Member
    ljharb commented Sep 3, 2018

    @TimothyGu that would require 4 try/catches around calling cached builtin methods, expecting them to throw for a true result. I would be very surprised if it was faster to do it in pure JS.

    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 3, 2018

    I just ran a simple benchmark only for boxed numbers and it's ten times faster in C++ (this is mixed input. Using only boxed numbers as input it's two times slower):

    tryNumberObject: 1255.014ms
    isNumberObject: 130.553ms
    

    Note: I optimized tryNumberObject before using it by setting the stackTraceLimit to 0. Otherwise it would be 20 times slower.

    @benjamingr
    Copy link
    Member

    @TimothyGu that would require 4 try/catches around calling cached builtin methods, expecting them to throw for a true result. I would be very surprised if it was faster to do it in pure JS.

    Would you mind explaining why @ljharb?

    @ljharb
    Copy link
    Member
    ljharb commented Sep 3, 2018

    @benjamingr because the only way to determine if something is a boxed primitive in JS, cross-realm, is to .call a brand-checking prototype method on it, and if it throws, it is one. (instanceof is never reliable) So, the algorithm would have to be basically this:

    1. return false if it's a primitive, or a function
    2. try/catch to see if it's got a Boolean slot, if so, return true
    3. try/catch to see if it's got a String slot, if so, return true
    4. try/catch to see if it's got a Symbol slot, if so, return true
    5. try/catch to see if it's got a Number slot, if so, return true
    6. return false

    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 4, 2018

    If no one objects to the current name in the next 24h, I'll go ahead and land it.

    The twitter poll resulted in 26% for the current name, 26% for isObjectPrimitive and 40% for isPrimitiveObject and 8% for isBoxedObject. Since the poll can not show how strongly people prefer either one, it's hopefully fine to just keep the current name.

    BridgeAR added a commit to BridgeAR/node that referenced this pull request Sep 5, 2018
    Checking all boxed primitives individually requires to cross the C++
    barrier multiple times besides being more complicated than just a
    single check.
    
    PR-URL: nodejs#22620
    Reviewed-By: Anna Henningsen <anna@addaleax.net>
    Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
    Reviewed-By: Refael Ackermann <refack@gmail.com>
    Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
    Reviewed-By: Gus Caplan <me@gus.host>
    Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
    Reviewed-By: James M Snell <jasnell@gmail.com>
    @BridgeAR
    Copy link
    Member Author
    BridgeAR commented Sep 5, 2018

    Thanks everyone.

    Landed in 3209679 🎉

    @BridgeAR BridgeAR closed this Sep 5, 2018
    targos pushed a commit that referenced this pull request Sep 6, 2018
    Checking all boxed primitives individually requires to cross the C++
    barrier multiple times besides being more complicated than just a
    single check.
    
    PR-URL: #22620
    Reviewed-By: Anna Henningsen <anna@addaleax.net>
    Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
    Reviewed-By: Refael Ackermann <refack@gmail.com>
    Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
    Reviewed-By: Gus Caplan <me@gus.host>
    Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
    Reviewed-By: James M Snell <jasnell@gmail.com>
    targos added a commit that referenced this pull request Sep 18, 2018
    Notable changes:
    
    * fs
      * Added a `recursive` option to `fs.mkdir` and `fs.mkdirSync`. If
        this option is set to `true`, non-existing parent folders will be
        automatically created.
        #21875
      * Fixed fsPromises.readdir `withFileTypes`.
        #22832
    * http2
      * Added `http2stream.endAfterHeaders` property.
        #22843
    * module
      * Added `module.createRequireFromPath(filename)`. This new method can
        be used to create a custom `require` function that will resolve
        modules relative to the `filename` path.
        #19360
    * url
      * Added `url.fileURLToPath(url)` and `url.pathToFileURL(path)`. These
        methods can be used to correctly convert between `file:` URLs and
        absolute paths.
        #22506
    * util
      * Added `util.types.isBoxedPrimitive(value)`.
        #22620
    * Windows
      * The Windows msi installer now provides an option to automatically
        install the tools required to build native modules.
        #22645
    * Added new collaborators:
      * boneskull (https://github.com/boneskull) - Christopher Hiller
    * The Technical Steering Committee has new members:
      * apapirovski (https://github.com/apapirovski) - Anatoli Papirovski
      * gabrielschulhof (https://github.com/gabrielschulhof) - Gabriel Schulhof
    
    PR-URL: #22932
    targos added a commit that referenced this pull request Sep 19, 2018
    Notable changes:
    
    * fs
      * Added a `recursive` option to `fs.mkdir` and `fs.mkdirSync`. If
        this option is set to `true`, non-existing parent folders will be
        automatically created.
        #21875
      * Fixed fsPromises.readdir `withFileTypes`.
        #22832
    * http2
      * Added `http2stream.endAfterHeaders` property.
        #22843
    * module
      * Added `module.createRequireFromPath(filename)`. This new method can
        be used to create a custom `require` function that will resolve
        modules relative to the `filename` path.
        #19360
    * url
      * Added `url.fileURLToPath(url)` and `url.pathToFileURL(path)`. These
        methods can be used to correctly convert between `file:` URLs and
        absolute paths.
        #22506
    * util
      * Added `util.types.isBoxedPrimitive(value)`.
        #22620
    * Added new collaborators:
      * boneskull (https://github.com/boneskull) - Christopher Hiller
    * The Technical Steering Committee has new members:
      * apapirovski (https://github.com/apapirovski) - Anatoli Papirovski
      * gabrielschulhof (https://github.com/gabrielschulhof) - Gabriel Schulhof
    
    PR-URL: #22932
    targos added a commit that referenced this pull request Sep 20, 2018
    Notable changes:
    
    * fs
      * Fixed fsPromises.readdir `withFileTypes`.
        #22832
    * http2
      * Added `http2stream.endAfterHeaders` property.
        #22843
    * util
      * Added `util.types.isBoxedPrimitive(value)`.
        #22620
    * Added new collaborators:
      * boneskull (https://github.com/boneskull) - Christopher Hiller
    * The Technical Steering Committee has new members:
      * apapirovski (https://github.com/apapirovski) - Anatoli Papirovski
      * gabrielschulhof (https://github.com/gabrielschulhof) - Gabriel Schulhof
    
    PR-URL: #22932
    targos added a commit that referenced this pull request Sep 20, 2018
    Notable changes:
    
    * fs
      * Fixed fsPromises.readdir `withFileTypes`.
        #22832
    * http2
      * Added `http2stream.endAfterHeaders` property.
        #22843
    * util
      * Added `util.types.isBoxedPrimitive(value)`.
        #22620
    * Added new collaborators:
      * boneskull (https://github.com/boneskull) - Christopher Hiller
    * The Technical Steering Committee has new members:
      * apapirovski (https://github.com/apapirovski) - Anatoli Papirovski
      * gabrielschulhof (https://github.com/gabrielschulhof) - Gabriel Schulhof
    
    PR-URL: #22932
    @rvagg
    Copy link
    Member
    rvagg commented Sep 20, 2018

    Is this only useful for isolated performance hacks? I'm trying to think of a use-case for it and can't, beyond the kinds of things that go in to util.inspect() as mentioned. Seems like an intentional leak on an abstraction that isn't intended to be leaky. Not a criticism, I'm just puzzled.

    @refack
    Copy link
    Contributor
    refack commented Sep 20, 2018

    Seems like an intentional leak on an abstraction that isn't intended to be leaky.

    > var x= new Boolean()
    > x
    [Boolean: false]
    > var y = false
    > y
    false
    > typeof x
    'object'
    > typeof y
    'boolean'
    > x === y
    false

    I believe it's just an aggregate of checks already available in the language. But yeah, util and util.inspect provide abstraction breaking footguns, but they are explicit about it.

    The util.inspect() method returns a string representation of object that is intended for debugging. The output of util.inspect may change at any time and should not be depended upon programmatically.

    Please note that util.inspect() is a synchronous method that is mainly intended as a debugging tool. Some input values can have a significant performance overhead that can block the event loop. Use this function with care and never in a hot code path.

    @jdalton
    Copy link
    Member
    jdalton commented Sep 20, 2018

    As a utility lib author this method is exciting because I so run into a need for this kind of check (equality checks, cloning, merging, etc) and would rather defer to an environment builtin when possible.

    @BridgeAR
    Copy link
    Member Author

    I did not implement it for performance reasons in the first place. I mainly want to have a utility function that makes sure I know if a variable is a boxed primitive or not. No matter what primitive it is. Otherwise I have a bloated code where I have to go through each boxed primitive manually / write an abstraction for it.

    Doing it in core seemed the best way of doing it because we do less boundary crossings and it fits well into all the other util.types functions. We have something similar with IsAnyArrayBuffer.

    @rvagg
    Copy link
    Member
    rvagg commented Sep 20, 2018

    OK, I get it, thanks for the insight folks!

    @ljharb
    Copy link
    Member
    ljharb commented Sep 21, 2018

    For those interested, I ended up making a package for this: https://www.npmjs.com/package/is-boxed-primitive

    @BridgeAR BridgeAR deleted the add-is-boxed-primitive branch January 20, 2020 11:37
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    author ready PRs that have at least one approval, no pending requests for changes, and a CI started. c++ Issues and PRs that require attention from people who are familiar with C++. semver-minor PRs that contain new features and should be released in the next minor version. util Issues and PRs related to the built-in util module.

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    0