Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Nothing.js – A chainable mock object which always returns itself (github.com/slmgc)
89 points by slmgc on March 31, 2018 | hide | past | favorite | 40 comments



One thing I've been finding increasingly while using Flow in javascript is, null-pointer exceptions aren't really much of a problem any more.

Where they might have caused a bug, 90% of the time Flow will catch them, and insist that I do a null check and handle that case.

And when they do crop up, it's usually more of a serious failure that I'm happy to trigger an exception early, rather than having it propagate further down the stack by using something like nothing.js. Especially given the list of gotchas. The fact that `Nothing` is not falsy is really problematic.

I'm pretty convinced at this point that `a && a.b && a.b.c && a.b.c.d` is an anti-pattern in javascript anyway; if I'm that unsure about the structure of my data, I have more serious problems than just trying to avoid null pointers.


Usually things of form `a && a.b && a.b.c && a.b.c.d` are a sign of a law of demeter violation. This applies to more than javascript. I view code like this as a sign that there might be a better way, as opposed to a stedfast rule. Refactoring to remove these types of violations tends to lead to better code.


Law of demeter applies to code, but in JS the deep property access is actually done often against data (deserialized JSON).


Just use try{} and keep the secret.


If you use Babel 7, you can install a plugin [0] to get the optional chaining syntax [1] that's currently a TC39 Stage 1 proposal!

[0] https://www.npmjs.com/package/babel-plugin-transform-optiona...

[1] https://github.com/tc39/proposal-optional-chaining


Yes this is a much better alternative in my opinion.


Oh boy, this is just making me miss Swift anymore...


Wouldn't this swallow errors that you wouldn't want it to swallow? e.g. you try to access a property on a value that's null (which you think isn't null), and instead of getting an error, nothing happens. Idx[0] is a way better solution to dealing with the boilerplate of checking for null/undefined that has 0 runtime cost due to being compiled away by babel. It works with type systems, and you can even use it as a babel macro[1].

With it

    idx(props, _ => _.user.friends[0].friends)
compiles to

    props.user == null ? props.user :
    props.user.friends == null ? props.user.friends :
    props.user.friends[0] == null ? props.user.friends[0] :
    props.user.friends[0].friends
Optional chaining[2] would add this to JS at the language level by using ?. as the operator.

[0]: https://github.com/facebookincubator/idx [1]: https://github.com/dralletje/idx.macro [2]: https://github.com/tc39/proposal-optional-chaining


idx requires a transpilation step.

> you try to access a property on a value that's null (which you think isn't null), and instead of getting an error, nothing happens

You can check for Nothing if you know you shouldn't get one. It's not about swallowing errors, it's about removing try/catch and all this `a && a.b && a.b.c && a.b.c()` boilerplate code.

> Optional chaining[2] would add this to JS at the language level by using ?. as the operator

But it's not there yet and it will require a transpilation step for quite some time before all the major browsers will introduce this feature.

Nothing returns "safe" default values instead of null/undefined, which might come in useful in some cases. Also, it can be used in unit tests to mock some deeply-nested constructs.


I think this kind of access pattern is typically a code smell. Instead of carrying around arbitrarily nested values you should try to normalize it into a known and consistent shape when crossing serialization boundaries.

Proxy pays a big performance penalty, and I doubt it'll be improving any time soon. In addition, I believe it's not possible to polyfill Proxy. An alternative could be to just wrap the deep property access in a try/catch:

    function getQux (input) {
      try {
        return input.foo.bar.baz.qux()
      } catch (e) {
        return null
      }
    }


Yeah, my first thought when seeing that it uses Symbol and Proxy was that Proxy can't be polyfilled. But it looks like there's partial polyfills for it: https://www.google.com/url?sa=t&source=web&rct=j&url=https:/...


Wrapping you code into a try/catch block decreases performance even more, you don't want to do that. You might want to check it out: https://github.com/GoogleChrome/proxy-polyfill


This is a really neat idea, and is a lot more ergonomic than nulls or optionals in JS. Until optional chaining makes its way into the spec [0], this is the cleanest way I’ve seen to operate on maybe-null values.

[0] https://github.com/tc39/proposal-optional-chaining


It's also hell to debug as it silently always works and propagate.

It's good for unit testing though.


That’s true, but ostensibly you only use it in places where you expect nulls and don’t intend to debug anything. That’s why Smalltalk, C#, CoffeeScript, and probably JS and TS soon have null chaining.


Objective-C also allows nil to receive messages which then just returns nil also. I’ve always found this really natural in that you can assume something is either valid or null and avoid a whole lot of null checks this way. That said, I’ve never been terribly frustrated by other languages either which throw when calling methods on null. Maybe it’s because of heavy use of certain design patterns that make it so it’s never really a surprise?


It also means a chained call with an nil in it still complete from start to end. So it has additional overhead, and you better not have side effects in there.

All in all, I'd like to have some kind of "?" operator in Python, I do think the pros are most of the time greater than the cons.


Not a new idea, though. It’s basically the venerable Null Object pattern as documented by the Gang of Four book.


Perhaps use the hint parameter of Symbol.toPrimitive() to fix some gotchas like Nothing + 123 => '123'?

See MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Edit: Won't help, because hint == 'number' is only triggered if used with unary plus or minus. Disregard this comment.


Nope, hint won't help here as it gets `default` instead of `number`.


Interesting project, but I'd stick with idx[0] since I have transpilation setup most of the time anyway.

Also some of you could be interested in optional.js[1], it's Java's Optional for js, makes it arguably more pleasant to deal with null values, no help for chaining though!

[0] https://github.com/facebookincubator/idx [1] https://github.com/JasonStorey/Optional.js


And if you’re using TypeScript, theres also TSOption https://www.npmjs.com/package/tsoption


Ruby equivalent:

  class Whatever; def method_missing *_; self; end; end
Sample usage:

  irb(main):001:0> Whatever.new.foo.bar.baz - 2
  => #<Whatever:0x007fd7ed0a2a68>


Yep, it's way easier to implement in some other languages. Btw, is it callable as well? Can you do Whatever.new().foo.bar().baz with it?


Nope. Ruby doesn’t support the function call paren syntax for anything other than methods. You could do stuff like Whatever.new().foo.().bar[] though.


foo, bar, and baz are all methods here, so you can use parens.


`.foo.bar` are method calls (parenses are optional in Ruby)


This would really benefit from adding comments in the 'how to use' code samples, or at least using standard foos and baars to make it clear what is user code and what is part of Nothing.js.

For example it is not really clear if 'executeAction()' etc are part of Nothing.js. I assumed they were, but then the next sample seems to do property chain traversal transparently, without them.


I agree, examples can be (and will be) improved with additional comments.


Updated the readme, added a few comments, changed some names


I don't think I would be comfortable with production code that swallowed access errors by providing a guard post value. I feel like that would too easily slip through in places it shouldn't. To ensure it didn't happen you would need to include explicit checks everywhere, which sort of defeats the stated purpose of the library.

However, it is a handy feature for writing tests. I wrote a similar library for testing called magical-mock based on the python library. https://www.npmjs.com/package/magical-mock


Looks elegant, but am not going to sunset the homebaked library solution yet I don't think, because of the proxy and polyfill overhead, and also because i prefer to freely assign a specific value if nothing:

TS-like syntax for clarity:

  Function Nothing(vTestVar:any,  vIfnull:any, sObjPath?: string, bStrict?: boolean) {... return vResultOfCheck}


Funnily enough, I made something similar a couple of months ago, not as a replacement for a "nothing" value but as a quick-and-dirty object placeholder and black box for tracking function calls.

https://github.com/UQ-eLIPSE/kuro


This reminds me of my "arbitrary fluent interface" idea https://gist.github.com/micimize/064dc637103a6f3651b74380e04...


It looks very similar to Either functor (left which always return nothing). Looks cool though.



> The implementation uses Symbol and Proxy behind the hood so you might need to use appropriate polyfills...

Does anyone know of a working Proxy polyfill? I've never been able to find one.


It's not possible to polyfill.



1. You should probably add that link to your read me:

The implementation uses `Symbol` and [`Proxy`](https://github.com/GoogleChrome/proxy-polyfill) behind the hood so you might need to use apropriate polyfills in case you want to support older browsers.

2. Personally, I find the name Nothing a bit too long. Why not use a shorter name like None?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: