Remix.run Logo
The bloat of edge-case first libraries(43081j.com)
105 points by PaulHoule 15 hours ago | 124 comments
pash 12 hours ago | parent | next [-]

> We should be able to define our functions to accept the inputs they are designed for, and not try to handle every possible edge case.

Oh, look, somebody just re-discovered static typing.

jerf 2 hours ago | parent | next [-]

They've discovered how to write dynamically-typed code correctly, or at least, a philosophy of it. It's not "discovering static typing" because that doesn't come up in static type languages. (Typescript is, for this particular purpose, still effective a dynamically typed language.)

I remember writing Python and Perl where functions largely just aimed you passed them the correct types (with isolated exceptions where it may have made sense) years before JavaScript was anything but a browser language for little functionality snippets. It's a dynamic language antipattern for every function to be constantly defensively checking all of it's input for type correctness, because despite being written for nominal "correctness", it's fragile, inconsistent between definitions, often wrong anyhow, slow, and complicates every function it touches, to the point it essentially eliminates the advantages of dynamic language in the first place.

Dynamic languages have to move some responsibility for being called with correct arguments to the caller, because checking the correctness of the arguments correctly is difficult and at times simply impossible. If the function is called with the wrong arguments and blows up, you need to be blaming the caller, not the called function.

I observe that in general this seems to be something that requires a certain degree of programming maturity to internalize: Just because the compiler or stack trace says the problem is on line 123 of program file X, does not mean the problem is actually there or that the correct fix will go there.

iwontberude 2 hours ago | parent [-]

I thought parent commenter was making a joke but thanks to you I am not sure anymore.

WhyNotHugo 5 hours ago | parent | prev | next [-]

I’ve seen something similar happen in Rust as well (and I do consider it an antipattern).

Some libraries take a `TryFrom<RealType>` as input, instead of RealType. Their return value is now polluted with the Error type of the potential failure.

This is a pain to work with when you’re passing the exact type, since you basically need to handle an unreachable error case.

Functions should take the raw types which they need, and leave conversation to the call site.

_bent 4 hours ago | parent | next [-]

It's annoying, but not for the error handling. To the contrary, I think the error handling is actually improved by this pattern. If you manually convert beforehand you easily run into working with a Result<Result<T, E>, E>.

What I find annoying about the pattern is that it hinders API exploration through intellisense ("okay, it seems I need a XY, how do I get one of them"), because the TryFrom (sort of) obscures all the types that would be valid. This problem isn't exclusive to Rust though, very OO APIs that only have a base class in the signature, but really expect some concrete implementation are similarly annoying.

Of course you can look up "who implements X"; it's just an inconvenient extra step.

And there is merit to APIs designed like this - stuff like Axum in Rust would be much more significantly more annoying to use if you had to convert everything by hand. Though often this kind of design feels like a band aid for the lack of union types in the language.

phanimahesh a minute ago | parent | next [-]

The errors in the result might be different types and need different handling, so nested result might not be undesirable

quotemstr 4 hours ago | parent | prev [-]

Why not teach rust-analyzer this pattern as an ad hoc heuristic to use when finding completions?

_bent 5 hours ago | parent | prev [-]

It's definitely pretty annoying, though not because of the errors. Actually the errors might be the biggest benefit even. If the conversion fails I can't continue with the function call.

xg15 8 hours ago | parent | prev | next [-]

I think there is an important observation in it though: That dynamic, loosely-typed languages will let you create code that "works" faster, but over the long run will lead to more ecosystem bloat - because there are more unexpected edge cases that the language drops onto the programmer for deciding how to handle.

Untyped languages force developers into a tradeoff between readability and safety that exists only to a much lesser degree in typed languages. Different authors in the ecosystem will make that tradeoff in a different way.

renmillar 6 hours ago | parent | next [-]

In my experience, this only holds true for small scripts. When you're doing scientific computing or deep learning with data flowing between different libraries, the lack of type safety makes development much slower if you don't maintain strict discipline around your interfaces.

jmull 5 hours ago | parent | prev [-]

Static and runtime type checks are each specified in similar code. The bloat's the same.

ModernMech 2 hours ago | parent [-]

For this particular example where they have to do a runtime parse to do the string to number conversion, yes. But in general static type checks are resolved at compile time, so they incur neither runtime cost nor do they increase the size of the resulting code. This is the primary benefit of doing static type checking.

whilenot-dev 11 hours ago | parent | prev | next [-]

How do you type the min > max constraint though?

kuruczgy 11 hours ago | parent | next [-]

Well, if your language has a sufficiently strong type system (namely, dependent types), you can take proofs of some properties as arguments. Example in Lean:

  def clamp (value min max : Float) {H : min < max} : Float := ...
whilenot-dev 10 hours ago | parent [-]

Sure, but the author picked TypeScript nonetheless. TypeScript is not a runtime, but a mere type checker - JavaScript is the runtime and a highly dynamic language. This detail got somehow completely lost in the article, but is IMHO the main culprit why such validations aren't bad, or sometimes even preferred.

The article also skipped over the following related topics:

  - When would you wrap errors from lower levels as your own?
  - What does "parse don't validate" mean when a TypeScript library gets transpiled to JavaScript?
chongli 8 hours ago | parent [-]

You don’t need a runtime for dependent types. After type checking the types get erased during compilation.

whilenot-dev 6 hours ago | parent [-]

Nobody would question that, but publishing a JavaScript library means that anyone using plain JavaScript can make use of it. Even though you aren't ever in control of the toolchain of your library's users, it's still your responsibility - as library author - to take that differences into account. If you'd transpile your library from Idris to JavaScript and publish it, these validations just can't be neglected at runtime. Type systems are just another model of the world at runtime.

roenxi 10 hours ago | parent | prev | next [-]

If we're trying to solve problems with good design, use endpoint1 and endpoint2 and then the function sorts them. Having max and min is itself a bad design choice, the function doesn't need the caller to work that out. Why should the caller have to order the ends of the interval? It adds nothing but the possibility of calling the function wrong. So in this this case:

    export function clamp(value: number, endpoint1: number, endpoint2: number): number {
      return Math.min(Math.max(value, Math.min(endpoint1, endpoint2)), Math.max(endpoint1, endpoint2));
    }
vanviegen 8 hours ago | parent | next [-]

That would lead to unpleasant surprises. When calling the function from some loop and when the bounds are inclusive, it's pretty common for (correct) edge cases to exist where you'd call the function with end===start-1. The function would do the right thing by returning an empty set. You'd get duplicate/unexpected records in some cases, that may be hard to debug.

It seems like your approach is just trying to ignore programmer errors, which is rarely a good idea.

roenxi 7 hours ago | parent [-]

I have no horse in the race and would usually just implement my clamp function the way the article does. However, if the clamp function clamping a number is an unpleasant surprise, I'm not going to accept that it is the fault of the clamp function. This hypothetical loop is buggy code and should be rewritten to expect clamp to clamp.

It is a special type of madness if we're supporting a reliance on implementation specific failure modes of the clamp function when someone calls it with incoherent arguments.

RHSeeger 3 hours ago | parent [-]

> This hypothetical loop is buggy code and should be rewritten to expect clamp to clamp.

But it makes it harder for the developer to recognize that the code is buggy. More feedback to the developer allows them to write better code, with less bugs.

Your argument could be made in the same way to claim that static typing is bad; because the caller should be calling it with the right types of values in the first place.

atombender 4 hours ago | parent | prev | next [-]

This maps poorly to the mathematical concept of a closed interval [a, b], which can be written a ≤ x ≤ b for a set of x. An interval where a > b is usually a programming error.

To ensure only valid intervals are supported at the type system level, the function could perhaps be redefined as:

    function clamp(n: number, i: Interval<number>): number
Of course, you need to deal with the distinction between closed and open intervals. Clamping really only makes sense for closed ones.
whilenot-dev 10 hours ago | parent | prev [-]

So an implicit fallback, but make it explicit through good design. Haven't even thought about this as a principle, since type checking persuades me to avoid anything implicit, thank you!

thomasmg 11 hours ago | parent | prev | next [-]

Many libraries throw an exception, panic, or silently swap the parameters at runtime.

To detect this at compile time, you would need either min and max to be known at compile time, or a type system that supports value-dependent types. None of the popular language support this. (My language named 'Bau', which is not popular of course, support value-dependent types to avoid array-bound checks.)

Animats 11 hours ago | parent | prev | next [-]

In a compiled language, it takes one or two machine instructions to test

    assert!(b >= a);
Works in C, C++, Go, Rust...

Amusingly, nowhere in the original article is it mentioned that the article is only about Javascript.

Languages should have compile time strong typing for at least the machine types: integers, floats, characters, strings, and booleans. If user defined types are handled as an "any" type resolved at run time, performance is OK, because there's enough overhead dealing with user defined structures that the run time check won't kill performance.

(This is why Python needs NumPy to get decent numeric performance.)

whilenot-dev 10 hours ago | parent [-]

Sure, use macros in function bodies. That won't affect the function signature in any meaningful way for the type checker and remains a check at runtime only, doesn't it?

It seems like the point of the article was to not do that though, contrary to my own opinion, and I just wonder why...

Smaug123 8 hours ago | parent | prev | next [-]

Don't send `start` and `end`; send `start` and `lengthOfInterval`. (Whether that's a good idea in a given API is another question.)

amavect an hour ago | parent [-]

That trades "min <= max" with "min + interval <= MAXINTEGER":

  if(number < min) return min;
  else if(number < min + interval) return number; // "if(number < max)"
  else return min + interval; // "return max"
fph 6 hours ago | parent | prev | next [-]

You define an Interval type, and check the constraint in its constructor.

IshKebab 11 hours ago | parent | prev | next [-]

Some languages can do it, but most can't do you either throw an error or do something reasonable. In this case just returning min would be reasonable.

croes 6 hours ago | parent | prev | next [-]

If min and max aren’t user inputs maybe we should trust the developer that they know what they are doing.

sfn42 6 hours ago | parent | prev [-]

You don't need to. One if statement to check that is not a problem. The problem occurs when you have a bunch of other ifs as well to check all kinds of other stuff that a type system would handle for you like nullability, incorrect types etc.

Personally I just write JS like a typed language. I follow all the same rules as I would in Java or C# or whatever. It's not a perfect solution and I still don't like JS but it works.

DarkNova6 7 hours ago | parent | prev | next [-]

Yep…

‘’’ export function clamp(value: number | string, min: number | string, max: number | string): number { if (typeof value === 'string' && Number.isNaN(Number(value))) { throw new Error('value must be a number or a number-like string'); } if (typeof min === 'string' && Number.isNaN(Number(min))) { throw new Error('min must be a number or a number-like string'); } if (typeof max === 'string' && Number.isNaN(Number(max))) { throw new Error('max must be a number or a number-like string'); } if (Number(min) > Number(max)) { throw new Error('min must be less than or equal to max'); } return Math.min(Math.max(value, min), max); } ‘’’

quotemstr 9 hours ago | parent | prev [-]

> Oh, look, somebody just re-discovered static typing.

If you're going to smug, at least do it when you're on the right side of the technology. The problem the article describes has nothing to do with the degree of static typing a language might have. You can make narrow, tight, clean interfaces in dynamic languages; you can make sprawling and unfocused ones in statically-typed languages.

The problem is one of mindset --- the way I'd do it, an insufficient appreciation of the beauty of parsimony. Nothing to do with any specific type system or language.

gourlaysama 7 hours ago | parent | prev | next [-]

Putting API contracts aside, the problem is also that people use a package manager as if it was a code snippet manager.

As in, "how do I check if a string starts with a shebang" should result in some code being pasted in your editor, not in a new dependency. There is obviously a complexity threshold where the dependency becomes the better choice, but wherever it is it should be way higher than this.

hawk_ 7 hours ago | parent [-]

This is the gap that LLMs have been filling quite well now in my experience. There are these little isolated tasks that are described easily in natural language for which the idiomatic code snippet can be created and importantly modified by LLMs as needed. No need to pull in pesky dependencies. Though when to switch over to a full blown dependency is still a judgement call.

2muchcoffeeman 5 hours ago | parent [-]

This is such a weird comment to me. These are trivial functions that could be faster to code than describe. I’d hope people aren’t relying on AI for functions as simple as clamp.

I mean, people have been writing these simple functions over and over for decades when they just needed one or two things that importing a library wasn’t needed. I wasn’t aware there was a gap to be filled.

wavemode 3 hours ago | parent | next [-]

Clamp is a simple enough function that if you simply typed its name and arguments, copilot would autocomplete its body. No prompting needed, in that case.

hawk_ 3 hours ago | parent | prev [-]

Not clamp per se, but there are enough tiny snippets which can have off by one or other issues not relevant to when you think about the problem description.

goranmoomin 10 hours ago | parent | prev | next [-]

The post is complaining about a library for a problem that javascript had 12 years ago, was not a thing for 7 years, and the ecosystem moved on. Typescript was not a thing back then. (or more exactly, it was a small thing out of the all too many transpile-to-js languages, at least)

Yes, having a library named is-number looks very stupid until you look at the state of javascript in 2014. Look at issue is-number#1[0] if you’re interested.

The library is-arrayish exists because array-like objects are actually a thing in javascript.

About is-regexp: the author mentions that their library supports cross-realm values because it’s useful, but then says that it’s an edge case that most libraries don’t need to care about? The whole reason that the library exists is to cover the edge cases. If not needed, yes the consumers of the library would have just been using the simple instanceof RegExp check.

If you’re arguing that there are consumers of those libraries that are wrong, the post might at least make sense – the presented case here is that the writer of the clamp function is stupid, not the other way around. Having a function that determines if a string is a number is not stupid; it’s importing that function and creating a clamp function with the wrong type signature part that’s stupid. Especially when it’s 2025 and typescript is universal.

All of the libraries that are mentioned are like 10 years old at this point. I don’t think we have to beat the dead horse one more time.

[0]: https://github.com/jonschlinkert/is-number/issues/1

throwaway290 10 hours ago | parent [-]

> the presented case here is that the writer of the clamp function is stupid, not the other way around

So are you saying the author updated the implementation and added deprecation warning?))

And author is not "stupid". More like "strategic". popular npm package = money. this is why everybody falls over to write leftpads and stuff.

goranmoomin 9 hours ago | parent [-]

> So are you saying the author updated the implementation and added deprecation warning?

As far as I understand, the author wrote the clamp function by themselves. There is no clamp library that the author is arguing against.

In fact, it seems the library ‘clamp’ in npm is a library that does exactly what the author wants – no validation, assuming that the value is a number, and just returning a number.[0]

[0]: https://github.com/hughsk/clamp/blob/master/index.js

throwaway290 9 hours ago | parent [-]

I mean isnumber, isarray, that sort of stuff you were talking about. Maybe clamp is the exception if there is no builtin way to do it in js

franciscop 12 hours ago | parent | prev | next [-]

I've tried to optimize to avoid these before. The problem is that these libraries are normally not used directly, they are usually pretty low in the dependency tree (root?). The farther you go down in the dependency tree, the older the packages are, where rightfully there's some reluctance to update things for the sake of updating things, risking breaking a stable library. It's a tricky thing and not so easy as to just say "don't use npm's `is-array`, use `Array.isArray()` instead", since every JS/TS package author writing code in 2025 knows that already.

It'd be a lot more productive to ask the authors of the (normally bigger, user-facing) libraries that you use nowadays to avoid using these libraries with deep and outdated dependencies. e.g. I stopped using Storybook because of this, but I'm happy to see that they've been working on cleaning up their dependency tree and nowadays it's much better:

https://storybook.js.org/blog/storybook-bloat-fixed/

sebtron 11 hours ago | parent | prev | next [-]

> At this point, it seems clear to me we’ve just poorly designed our function. It solely exists to clamp numbers, so why would we accept strings?

To me it sounds like you are using a poorly-designed language

nenenejej 11 hours ago | parent | next [-]

Yes. Go for example doesnt need an is-nunber, nor do you need to worry about if a validation throws an exception and unwinds your call stack (or not)

quotemstr 9 hours ago | parent | prev [-]

> To me it sounds like you are using a poorly-designed language

Not language specific. A C example that comes to mind is checking every non-nullable pointer parameter in every function for NULL and reporting an error instead of just letting the program crash. (Please, don't do that: just let contract violations produce crashes.)

The article's author describes a problem of experience and spirit, not a language wart.

hnlmorg 8 hours ago | parent | next [-]

But the problem is literally a direct result of language wart.

The nullable pointer you’ve given is another great example of language wart. And one that’s often highlighted as a specific criticism against Go lang.

quotemstr 8 hours ago | parent [-]

No, the problem is a result of sloppy thinking --- or no thinking --- about function contracts.

hnlmorg 8 hours ago | parent [-]

Humans error. The point of programming languages is to provide an abstraction to make development faster and less error prone than writing stuff in lower level languages or even assembly.

If your language requires you to manually track function contracts then that’s a problem with the language itself. Not the developer.

Turskarama 9 hours ago | parent | prev | next [-]

Let me blow your mind for a second: this problem is not insurmountable in language design, C is not a perfect language, and nulls are bad design.

ModernMech 2 hours ago | parent | prev [-]

Indeed, you've pointed out a second language in the class of "poorly-designed languages"

MathMonkeyMan 12 hours ago | parent | prev | next [-]

This is what undefined behavior is for. You specify a contract for your function, and then the implementation of the function assumes that the contract is satisfied. It does not check, and does not try to diagnose errors.

It's not about performance, it's about separating concerns: the caller handles the not-a-number case, so the callee doesn't have to.

Then you add optional runtime checking of the contract, preferably in a different build mode (or a different version, e.g. "my-lib1.2-debug"), to get sensible diagnostics quickly in tests and canary deployments. The checks are redundant by definition. Defensive programming.

sesm 7 hours ago | parent [-]

Also known as "garbage in - garbage out" approach. That's how Clojure standard library is designed.

pwdisswordfishz 4 hours ago | parent [-]

And C/C++.

moomin 10 hours ago | parent | prev | next [-]

Problem is, there’s a well known principle of library design: any behaviour you exhibit eventually becomes something someone relies upon. You can choose to live with silent breakage or you can validate your inputs within an inch of their lives. Next, with the best will in the world, even with a small library, you quickly discover a wide range of use cases that should reasonably be supported by your library. So all libraries end up complex, not just because of validation.

Yes, static typing would help with some of this, but very much not all.

zahlman 10 hours ago | parent | prev | next [-]

Two common ideas I see ITT are that the problem is caused by JS not having either a) static typing or b) a large standard library.

I don't understand this. Python lacks static typing, but it also doesn't have a need for the kind of type-checking that is-number and is-array perform — it's how Python's dynamic typing already works. What JavaScript is missing is strong typing: i.e. it's "fault tolerant" by performing implicit conversions everywhere and minimizing the chance of control flow ever being disrupted by an exception, presumably intended to maximize the chance that the user sees some kind of DOM update rather than the page ceasing to function. Python sidesteps these issues by just raising `TypeError` instead, and by making proper consideration of exception handling an expected part of the programmer's job. The boilerplate in TFA's opening example is essentially what the Python runtime already does.

Similarly, Python has a large standard library, but it's irrelevant to the problems described. The Python standard library doesn't solve the problem of determining whether something is "a number" or "an array"; trying to use it that way does. The Python standard library doesn't solve the problem of determining whether something is "a regex"; if anything, JS' functionality here is more native because you don't need the standard library to create a regex.

As for the "pascalcase" example, the standard library isn't really helping Python there, either. There are multiple third-party libraries for string-casing operations in Python; there are some things you can do first-party but they're almost all methods of the built-in string type rather than part of the standard library. Aside from some useful constants like "a string with all the ASCII letters in it", the `string` standard library implements two failed attempts at string formatting routines that weren't popular even when they did solve real problems, and a "capwords" function with subtly different semantics from the "title" method of strings. Which has existed since at least 2.0.

(Granted, Python documentation has separate "library" and "language" categories, and classifies the built-in types and their methods as "library". But when people talk about the importance of a "large standard library" in a language, I generally understand that they're thinking of code that has to be explicitly pulled in.)

phito 10 hours ago | parent | prev | next [-]

I've only seen these kind of libraries in JavaScript. They are a direct result of poor language design.

procaryote 7 hours ago | parent [-]

Nothing in javascript makes you have tiny pointless libraries that check if something is odd or if something is an array(ish). This is just a quirk of javascript developer culture

At some point someone went "let's decouple as much as we can! A library should be just a single function!" and we've spent a lot of time since then showing why that's quite a bad idea

The lack of types perhaps inspires some of these functions masquerading as libraries, but they're often trivial checks that you could (and probably should) do inline in your less pointless functions, if needed.

pornel an hour ago | parent [-]

Properties of a language shape the tooling and culture that develops around it.

JS has exploded in popularity when Internet Explorer was still around, before ES6 cleanup of the language. JS had lots of gotchas where seemingly obvious code wasn't working correctly, and devs weren't keeping up with all the dumb hacks needed for even basic things. Working around IE6's problems used to be a whole profession (quirksmode.org).

Browsers didn't have support for JS modules yet, and HTTP/1.1 couldn't handle many small files, so devs needed a way to "bundle" their JS anyway. Node.js happened to have a solution, while also enabled reusing code between client and server, and the micro libraries saved developers from having to deal with JS engine differences and memorize all the quirks.

minaguib 43 minutes ago | parent | prev | next [-]

Entirely unrelated to the topic, but my mind was blown when I recently learnt that clamp() can be implemented via .median(val, low, high), and the param order doesn't matter!

pizlonator 2 hours ago | parent | prev | next [-]

It’s interesting how multiple of the bad cases the post identifies exist because of how gross the dynamic types in JS are.

If JS’s dynamic types worked like Ruby’s or Python’s then you wouldn’t think to build a library that tells you if something is a regex.

BobbyTables2 13 hours ago | parent | prev | next [-]

How are the download rates of these things so high?

Are there 1000 people running 100 CI pipelines/day where downloads aren’t cached?

swiftcoder 8 hours ago | parent | next [-]

Yep, every single transitive downstream consumer pulls a fresh copy on every CI run. It's kind of ridiculous, and it's like this in every modern ecosystem.

For example, this is a completely empty rust crate, which I started years ago and never released, and it's still downloaded multiple times per day... https://crates.io/crates/ruble

dwoxctbvgq 6 hours ago | parent | next [-]

Could it be caused by Crater runs?

https://github.com/rust-lang/crater

ModernMech 2 hours ago | parent | prev | next [-]

Oh so you're the reason we got btleplug!:P

https://github.com/deviceplug/btleplug

fwiw the downloads are probably automated crates.io indexers.

fainpul 8 hours ago | parent | prev [-]

I'm confused. Why did you publish this? Who "uses" it?

swiftcoder 8 hours ago | parent [-]

I published it purely to placeholder the name (and then life got in the way, and I never released the actual library). Nobody uses it at all, but some CI system is still polling at regular intervals...

BobbyTables2 2 hours ago | parent | next [-]

Wonder if someone has an internal crate named the same, but yours is getting pulled down for some tertiary use (license check, etc).

Stuff like Docker that defaults to downloading things from Internet repos is kinda scary. There isn’t always a clear dividing line between the thing as a “tool” and the thing as a “repo”.

At least with “git”, I know they I’m not going to end up cloning an artifact from an Internet repo just because I made a small typo…

fainpul 6 hours ago | parent | prev | next [-]

But if no other crate depends on your crate and nobody directly uses it, why would any CI system download it?

swiftcoder 4 hours ago | parent [-]

I think some of the CI systems just monitor all published crates. For example, docs.io potentially pulls it to see if there is any new documentation

qludes 6 hours ago | parent | prev [-]

So someone could reserve all the pronounceable crate names?

swiftcoder 4 hours ago | parent [-]

Yeah, it’s a known problem in the (non-namespaced) crates.io ecosystem. If someone else wants this name for their Bluetooth LE crate, I’ll happily hand over the keys, but obviously not every crate-squatter is so principled

kingstnap 13 hours ago | parent | prev | next [-]

Yep. True webscale is astronomical levels of inefficient and wasteful usage of bandwidth and CPU.

nerdponx 11 hours ago | parent | prev | next [-]

It doesn't help that container images typically require you to "opt in" to sharing a cache with the host system. Docker even has a special file mount type for it. I find that people on average don't have a lot of interest in making such things work, they see that it's not available right in front of them and shrug and say "oh well" because ultimately it doesn't hurt them much.

phito 10 hours ago | parent [-]

Yeah I'd say most people view it as such, until they hit (docker hub) rate limits.

zahlman 10 hours ago | parent | prev | next [-]

It seems to happen in the Python world, too. Check out the stats for the most commonly downloaded packages from PyPI (https://pypistats.org/top), and also consider which packages are on the list.

(Pip used to be right up there next to setuptools. It's still pretty popular — https://pypistats.org/packages/pip — but uv has clearly changed the equation.)

GenerocUsername 13 hours ago | parent | prev [-]

Yes

rossant 2 hours ago | parent | prev | next [-]

> I don’t really agree with this and think downloading a package for #! 86 million times a week is a bit much.

Yeah. A bit.

larusso 10 hours ago | parent | prev | next [-]

Over 10 years ago I was a bit more invested in the ruby environment and the whole duck typing thing. I really liked dynamically typed languages. One thing I remember was when learning rsync that other libraries loved to write specs where all kinds of types and values gets thrown at a function to see how it behaved. Since these languages have no compile step one needs to check how they do at runtime. I remember I picked up a pattern to check if something is looking like a string or number or can be turned into Omer etc. It definitely blows up your test specs but also makes your awesome function even more flexible for the future. Hey you never know what you throw at it tomorrow. Maybe you need to call it with an a number array in string format. I stopped doing that and concentrated to test for the types I expect. Which really breaks with the typeless nature of a language but that is more philosophical. Needless to say I think all these helper packages are way overboard. There is also the tendency under programmers to really “never repeat yourself”. Which ends up in a utility function that has to be introduced into every library or project and starts to grow because of different edge cases from all the different use-cases. Let’s add some flags and overloads. Not excepting the fact that it is sometimes cleaner to just have a copy with special adjustment of code then desperately trying to generalize it.

twosdai 4 hours ago | parent | prev | next [-]

I feel like the type guarding programming method present in some packages, is a hangover from node js development prior to typescripts rise in popularity.

So now some people just do it by habit.

Honestly the only place having code like that I see being useful is at the controller level inside an api or other untrusted input validation for an application.

afc 11 hours ago | parent | prev | next [-]

I wrote a somewhat related article, after running just this Friday into two blocks of code that were deliberately causing invariant violations (akin to the "max < min" situation) to be ignored silently: https://alejo.ch/3gk - Fail loudly: a plea to stop hiding bugs

dccoolgai 3 hours ago | parent | prev | next [-]

Always interesting to think about Postel's law in the context of these kinds of things. ("Be liberal in what you accept and conservative in what you send")

b_e_n_t_o_n 14 hours ago | parent | prev | next [-]

JavaScript had to implement these checks because you could pass a function literally anything and it was on the implementation to deal with it. Typescript has this same problem tbh.

Spivak 13 hours ago | parent | next [-]

I mean they clearly don't have to, Python has the same problem of being able to pass anything—you don't see this kind of incredibly defensive programming. But Python set a culture of being for "consenting adults" where it's expected that callers will read the docs and ensure preconditions are satisfied, and if they don't whatever happens, happens.

It leads to less code, and more generalizable code. Maybe the caller does know what they're doing, who am I to say they can't.

pcwelder 12 hours ago | parent [-]

>if they don't whatever happens, happens

What happens is you get an error. So you immediately know something is wrong.

Javascript goes the extra mile to avoid throwing errors.

So you've 3>"2" succeeding in Javascript but it's an exception in python. This behavior leads to hard to catch bugs in the former.

Standard operators and methods have runtime type checks in python and that's what examples in the article are replicating.

recursivecaveat 11 hours ago | parent [-]

It's so strange to me that JS goes so far to avoid errors, mashing random stuff into strings or NaNs, misspelled variables just become undefined, etc. Then when your off-the rails program reaches `undefined()` it's "woah woah woah, that's clearly nonsense, stop program execution right this instant". I feel like I could respect it a little more if they committed to "a webpage should never crash under any circumstances" and there were just no exceptions in the language whatsoever.

watwut 11 hours ago | parent [-]

It has nothing to do with the wish to not crash page.

Javascript was a prototype that was supposed to be refined and cleaned. Then management said "we will ship it now" and it was shipped as it was.

At the time, they thought it will be used for small things, minor snippets on web page. Webapps as we have them were not a thing yet.

quotemstr 9 hours ago | parent | prev | next [-]

> you could pass a function literally anything and it was on the implementation to deal with it

So? Language is irrelevant here. What matter is the contract, the agreement that operates through time and space between caller and callee on behavior and expectations. Contracts exist outside of the type system. All a type system can do is enforce a subset of the contractual requirements.

For example, log(0) is undefined. If you write a function to compute log(x), you can (and should) document the function as having unspecified behavior when x is zero.

That's the contract.

The contract holds whether the function is spelled like this

  // x must be positive number
  function log(x) { return ... }
or

  // x must be positive
  function log(x: number): number { ... }
In the second example, we have the type system enforce the "is a number" rule, but at least in TypeScript, you can't use the type system to check that x is positive. In some languages you can, but even in those languages, programs have invariants the type system can't express.

Likewise, you could define a version of log that accepted, e.g. spelled numbers, like log("three"). You can express this weird contract with static or dynamic typing.

The nasty thing is that if early in the log library's history, before the author wrote down the contract or even had TypeScript, someone had written log("three") and gotten a type error, the log library's author might have "fixed" the "bug" by checking for spelled numbers as inputs. That would have been an expansion of the function's contract.

The log library author probably wasn't thinking about the long-term downsides of this expansion. He could have, even when our log() was a pure-JavaScript, just said "The contract is the input is a positive number. Your code is buggy."

But our hypothetical log library author didn't have the experience or good judgement to say no, so we ended up with the kind of "corner case bloat" the article's author describes.

umanwizard 12 hours ago | parent | prev [-]

> Typescript has this same problem

Don’t most languages have the same problem? Even C or Rust have escape hatches that let you override compile-time type checking and pass whatever gibberish you want to a function. How is Typescript any worse?

b_e_n_t_o_n 12 hours ago | parent [-]

node main.js just runs the code. you could simply not run tsc, which plenty of people do. can you ignore all type errors in Rust?

IshKebab 10 hours ago | parent | next [-]

You can ignore the borrow checker at least by using mrustc. And some type checks at least could be optional. The fact that they aren't isn't really a fundamental property of Rust, it's just because it's easy to make them not optional.

In my experience nobody writes Typescript without checking the types. Unlike Python for example where it's not uncommon to have broken types. (And it's way more of a mess in general.)

umanwizard 9 hours ago | parent | prev | next [-]

If you don’t run tsc you are not really using typescript.

nevir 12 hours ago | parent | prev [-]

The Rust equivalent is more like using `unsafe` and derefing raw pointers

b_e_n_t_o_n 12 hours ago | parent [-]

Yeah and you can explicitly assert a null is a string in TS, but it's explicit. You can't build a Rust program without those asserts but it's trivial to skip the type checking for TS which is more of a linter than a type system.

chamomeal 12 hours ago | parent [-]

I disagree that TS is more of a linter. But I definitely feel sympathetic to that perspective.

I’ll pull off the cleanest, nicest generic constraints on some component that infers everything perfectly and doesn’t allow invalid inputs, just for a coworker to throw @ts-nocheck on the entire file. It hurts

hk__2 5 hours ago | parent | prev | next [-]

Interesting but the article never takes the time to really explain what exactly is the problem with using these libraries.

Havoc 9 hours ago | parent | prev | next [-]

The wild part is that a post about bloat concludes with here is more crap to install to visualize your JS stack.

Starting to think that JS based front end is a trashfire top to bottom. Not this framework vs that framework...the entire thing

vitonsky 7 hours ago | parent | prev | next [-]

Well, somebody must validate data on top level. So that's fine to have packages like `is-number`/`is-arrayish` etc.

kamma4434 6 hours ago | parent | prev | next [-]

I think the author has this wrong. Handling a ton of edge cases so I don’t have to do it myself is a good reason to use a library. Importing an endless tree of deps I have no control of - that is the issue.

tobr 12 hours ago | parent | prev | next [-]

I would be a little upset if a clamp function could throw a validation error when I passed only numbers. I’ve always written clamp to work even if min is larger than max. Basically, clamp comes down to sorting the three numbers and returning the middle one. I’ve never thought of that as an edge case, it just seems like a more general way to think of the operation that gives you less surprises since every possible input has a well-defined output.

IshKebab 10 hours ago | parent | next [-]

> clamp comes down to sorting the three numbers and returning the middle one

That is a really weird definition of clamp. I would expect to consistently get either min, max, or an error.

I bet Rust doesn't use your weird idea... Yep. Fully sane implementation:

https://docs.rs/num/latest/num/fn.clamp.html

tobr 6 hours ago | parent [-]

It’s not a definition, it’s an implementation. A definition would be something like ”constrain a number to an interval defined by two other numbers”. That happens to be identical to sorting and picking the middle value. The only difference vs OP’s clamp is that it gives a consistent and predictable result for ”inverted” intervals where min > max, instead of arbitrarily deciding how to collapse the interval.

IshKebab 3 hours ago | parent [-]

It is a definition. You've defined the behaviour when min>max and you've chosen a weird way to define it.

nerdponx 11 hours ago | parent | prev [-]

That's clever, but it's not a common perspective so the behavior you propose might be quite surprising indeed. And moreover it only applies to this one particular function. The general principle still applies in general.

meindnoch 7 hours ago | parent | prev | next [-]

clamp should actually accept anything comparable. E.g. clamp("foo", "bar", "baz") should return "baz".

Minor49er an hour ago | parent [-]

Why wouldn't it return "foo" instead?

MagicMoonlight 5 hours ago | parent | prev | next [-]

Notice how this slop doesn't happen in real languages like C++ or Java. It's only the slop languages.

You don't need a "is it a number" package in java, because the function cannot be passed a string, because it actually has a type system. All this slop has come about because meme developers refuse to accept the truth - programming languages have proper syntax for a reason.

PaulHoule 3 hours ago | parent [-]

Partially true.

A lot of times you have something in Java which could be

   ReturnValue someMethod(Parameter p) {
      …
      var x = anotherMethodThatMightThrowAnException(q);
      …
   }

and I’m going to argue that often that’s good enough but a lot of people think it isn’t so they might put 20 lines of code above that line to head off that exception or they might put it in a try-catch block [1] to respond to it or wrap it. Those unhappy paths bloat your code the same way as the guards in the blog post, make it unclear how the happy path works at a glance (place for bugs to hide in the happy path) and obviously create opportunities for bugs in the unhappy path.

I’ll argue that (1) error handling is a function of the application as a whole or of a “unit of work” (e.g. database transaction) so unless you’re at the top level method of a unit of work you should “let it throw”, (2) try-finally is preferable to try-catch when it comes to cleaning up and tearing down, and (3) it is fair to wrap and rethrow exceptions if you want the unit of work manager to have more context to know what to do, or provide a better error message.

In theory, you could spend time writing and maintaining error preempting and handling code and get better error messages. However that code isn’t always correct. One funny thing about AI agent programming for me is that if I ask Junie to code me some tests it works harder at testing unhappy paths than I would.

khaledh 13 hours ago | parent | prev | next [-]

This is why every language needs a good standard library, which unfortunately JavaScript never had.

eduction 12 hours ago | parent | prev | next [-]

Incredibly shallow post. The level of validation and defensiveness in any piece of code will generally have to do with how close to the edge of your application it is — that is, how close to the input.

Functions near the core can assume more and code less defensively than those near the edge.

Libraries shouldnt be edge case first? What? /What kind/ of libraries? You really can’t imagine a library where it makes sense to code very defensively?

Also the post doesn’t even use “edge case” correctly. Definitionally you can’t have an edge case before you’ve defined the acceptable input. In their clamp function the edge case would be something like a large number on the border of graduating from long to BigInt or something. Edge case does not mean invalid input. Edge case is a rare/extreme input that is valid.

croes 6 hours ago | parent | prev | next [-]

Those kind of function are like a second developer who doesn’t trust the developer of the program.

Have a little faith they validate the input and give the right min and max before they call your function or let them fail to learn.

01HNNWZ0MV43FF 14 hours ago | parent | prev | next [-]

3 of those 4 examples wouldn't exist in a statically-typed language like TypeScript

ants_everywhere 14 hours ago | parent [-]

Yes this is just ad hoc runtime type checking

mewpmewp2 13 hours ago | parent [-]

Alternative -> could just use something like Zod or have native stdlib Zod like type of thing to improve a lot on this type of edge case handling. Although if performance is key, then might question how much this constant parsing and validation would add on top, but in 99% cases probably wouldn't matter.

0x20cowboy 14 hours ago | parent | prev [-]

I agree with the spirit of this, but I’d go one more step with the example:

export function clamp(value: number, min: number, max: number): number { return }

That is just adding an extra jump and entry on the callstack where you could just have done:

Math.min(Math.max(value, min), max);

Where you need it.

(The example used was probably just for illustration though)

pigbearpig 13 hours ago | parent | next [-]

The author does acknowledge that in the "How it Should Be" section.

chii 13 hours ago | parent | prev [-]

> you could just have done ...

i disagree with this particular example - it's actually a good use case for being a utility maths function in a library.

I mean, the very `min()` and `max()` function you used in the illustration could also have been used as an example, if you use the same logic!

mewpmewp2 13 hours ago | parent [-]

I'm not native English, but I've done coding for way more than 10+ years, yet I would be able to understand and read quicker Math.min(Math.max(value, min), max) compared to registering in my brain what exactly "clamp" does.

In terms of DRY and cleanliness, yes "clamp" sounds awesome. But in terms of practicality and quick understanding? Math.min and Math.max there is such a frequent pattern that brain immediately understands what is being tried to do there, as opposed to exact meaning of "clamp" to me.

It may be just me though, clamp is not a word I frequently hear in English, and I see it in code sometimes, but not frequently enough to consciously register what it does as fast in my brain. Despite seeing code for probably 8h+ a day for the past 10 years.

If it was in std lib of JS, maybe then it would be better.

Like there is some sort of balance line on how frequently something is used and whether it's REALLY worth to abstract it. If it's in stdlib, use it, if it's not use it if truly it's pretty much always used.

xmprt 13 hours ago | parent [-]

Maybe it's different since you're not native English but clamp is as intuitive for me as something like ceil or floor methods. It might not be as simple for someone who is ESL, probably because no major programming languages that I know of include clamp in their standard library like they do with min, max, ceil, floor.

I'm also coming from being a Go programmer where the native library is very barebones so a lot of custom utility methods like this are inevitable.

aero_code 12 hours ago | parent | next [-]

FYI, some major programming languages have clamp:

C++: https://en.cppreference.com/w/cpp/algorithm/clamp.html

.NET/C#: https://learn.microsoft.com/en-us/dotnet/api/system.math.cla...

Java: https://docs.oracle.com/en/java/javase/24/docs/api/java.base...

Ruby: https://ruby-doc.org/core-2.4.0/Comparable.html#method-i-cla...

Rust: https://doc.rust-lang.org/stable/std/primitive.f64.html#meth...

webstrand 12 hours ago | parent [-]

Even CSS has clamp. I have to periodically remind myself that Math.clamp is missing from the stdlib, it's much easier to read when you need to use a bunch. Math.min(lower, Math.max(value, upper)) is fine for a one-off, but when you need multiple instances of clamp in the same expression the extra parentheses become troublesome.

zahlman 9 hours ago | parent [-]

I do find it strange how Python's min and max are builtin and don't require an import, but 'clamp' isn't provided at all. When I look at https://stackoverflow.com/questions/4092528 I feel the lack of an "obvious way to do it", since the simple approach has variant spellings and there are always people trying to be clever about it.

TRiG_Ireland an hour ago | parent | prev [-]

I am a native English speaker, and I've never heard of "clamp" before. Admittedly, most of my programming experience is in PHP, and I've rarely done anything with much maths.