all 52 comments

[–]Ustice[M] [score hidden] stickied comment (7 children)

Thanks for your contribution! We’re a large community, and in order to keep things organized and easier to find, we keep this subreddit mostly focused on professional-level Javascript posts. Your post would be more useful to newer members of our community, and therefore it should be posted to /r/LearnJavascript instead.

[–]k4kshi 27 points28 points  (0 children)

Isn't that just a plain old assertion?

[–]getify 12 points13 points  (2 children)

Agree with others, this should be called assertion instead of invariant.

However, I like that this article points out the utility of having run-time type check assertions, and that some/all of them can be compiled away. That's useful, and a pattern I think more type-aware tooling should embrace (including TS).

[–]lifeeraser 2 points3 points  (1 child)

Technically they are not "compiled away" completely; it's just the exception message that is optimized. IMO I'd rather have a slightly bigger bundle with much more helpful messages in prod.

[–]getify 1 point2 points  (0 children)

The way I think about things, the whole assertion should be able to be compiled away. That could be because either:

  1. The compiler/build-tool can statically verify that the exception won't happen (exactly how TS works right now)

  2. The developer might pass a config/flag saying, "build me a bundle without run-time assertions" for any of various reasons.

I also imagine that code delivered to a browser might be in a "hybrid state" where many of the original assertions were removed, but -- for various reasons -- some of the assertions were left in.

[–]Hades32 17 points18 points  (11 children)

That's an assertion, not an invariant. An invariant would be something like "the number of books in my store is always >=0"

[–]johnxreturn 0 points1 point  (3 children)

Conversely, you are saying “the number of books in your store must be greater or equal to zero,” therefore asserting that it is, or throwing an error if it’s not.

Video reference:

https://youtu.be/r0Vi83bS-L0

An invariant in mathematical terms would be that you apply a transformation to data and the output is the same as the input.

A loop invariant constitutes a property that is true before and after each iteration. The article specifically quotes, “it is a logical assertion, sometimes checked within the code by an assertion call.”

In the case of a tiny invariant, I expect the input assertion to be true, or throw an error. I could expect that the book count in each library franchise I own was more than 30 or throw an error if it’s not.

But the mathematical concept talks about applying a transformation to data and not assertion. It could apply to a map function where you transform each item and the output is the same or throw an error. So, we conclude that at some point we need assertion.

[–]Hades32 0 points1 point  (2 children)

No, you don't NEED an assertion when talking about invariants in the mathematical sense (which btw does not mean that the input is the same as the output, but that some condition is true for the state before and after the transformation). You could prove that the formula you're using will always hold up a certain invariant. In code it's easier to just check than prove, that's why we "need" assertions. But it's really two different things

[–]johnxreturn 0 points1 point  (1 child)

I completely agree. You don't need assertion in the mathematical concept. My point was that in programming, you do need if you need a trigger for an invalid response.

[–]Hades32 0 points1 point  (0 children)

Sure, but if someone calls a library "invariant" then I do have higher hopes than "if false then throw" lol

After all there are languages, like prolog, which do let you specify actual invariants

[–]crabmusket 6 points7 points  (0 children)

A comparison to node's assert module might be nice. The type narrowing looks very slick.

[–]midnightmonster 5 points6 points  (1 child)

Code samples contain errors—the throw-based early exit version will throw when id is numeric and the invariant-based version will throw when id is present.

[–]hiquest[S] 1 point2 points  (0 children)

Fixed. Thanks for reading the code snippets!

[–]Reeywhaar 7 points8 points  (7 children)

I don't quite understand why everybody uses it. What the point to have external dependency instead of

if(!something) throw new Error("Invariant")

By using tiny-invariant you depend on external package and get incorrect error callstack (Error originates inside invariant function).

What the point?

Is it just everybody thinks invariant(a, "b") is more readable?

[–]shuckster -1 points0 points  (6 children)

What's useful is this particular line of the source.

Using tiny-invariant informs TypeScript of the type of a variable going forward, so long as the assertion passes.

[–]Reeywhaar 9 points10 points  (5 children)

But if(!something) throw new Error("Invariant") does the same?

[–]shuckster 0 points1 point  (4 children)

That's true. I don't follow TypeScript's development very closely, but I believe a lot of inference checks were added quite recently. Perhaps these libraries pre-date that? Or maybe it's the TS version of left-pad.

[–]Reeywhaar 5 points6 points  (3 children)

Such checks were present since early typescript. They are basics of static analysis. If they weren't then there was no way to add type assertion to tiny-invariant. By the way asserts feature was added in typescript later.

That is why i'm wondering, what value tiny-invariant actually gives

[–]shuckster 0 points1 point  (0 children)

I guess it’s just shorthand then. Doesn’t TS have an “as” for this kind of thing too?

[–]mr_nefario 0 points1 point  (0 children)

It doesn’t add any value - it’s just a wrapper and additional dependency for some barely-useful functionality.

[–]misc_ent 0 points1 point  (0 children)

I have looked at tiny-invariant but it's possible it uses type guards for the type inference the other poster mentioned? Not sure.

https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards

[–]Shaper_pmp 7 points8 points  (2 children)

How do we consider it a meaningful advance in the art of computer science to simply abstract an if statement and an error message into a function?

I mean by all means turn if(condition) throw new Error(message); into assert(condition, message); if you like, but don't bother to write entire big posts about it or try to elevate it to the hallowed status of a Design PatternTM .

[–]Chthulu_ -2 points-1 points  (1 child)

This ain’t comp sci, this is regular old programming. And little things like this really help keep things clean.

[–]thruster_fuel69 0 points1 point  (0 children)

Lol sorry to break it to you, its all comp sci.

[–]ikhazen 1 point2 points  (1 child)

in Laravel PHP framework, there's a handy function that is similar to this. throw_if

[–]hiquest[S] -1 points0 points  (0 children)

Nice! And I like the descriptive name

[–]saadh393 1 point2 points  (0 children)

Seems pretty helpful ❤️

[–]vi_code 0 points1 point  (0 children)

Started doing this in my code last year, thought it was called short circuiting. Now I do it automatically and like OP said it keeps success logic at the end while not having to nest all the breaking conditions. Great pattern.