Readit News logoReadit News
pash · 5 months ago
> 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 · 5 months ago
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 · 5 months ago
I thought parent commenter was making a joke but thanks to you I am not sure anymore.
WhyNotHugo · 5 months ago
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 · 5 months ago
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.

_bent · 5 months ago
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 · 5 months ago
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 · 5 months ago
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 months ago
Static and runtime type checks are each specified in similar code. The bloat's the same.
whilenot-dev · 5 months ago
How do you type the min > max constraint though?
roenxi · 5 months ago
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));
    }

kuruczgy · 5 months ago
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 := ...

Animats · 5 months ago
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.)

thomasmg · 5 months ago
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.)

fph · 5 months ago
You define an Interval type, and check the constraint in its constructor.
sfn42 · 5 months ago
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.

Smaug123 · 5 months ago
Don't send `start` and `end`; send `start` and `lengthOfInterval`. (Whether that's a good idea in a given API is another question.)
croes · 5 months ago
If min and max aren’t user inputs maybe we should trust the developer that they know what they are doing.
IshKebab · 5 months ago
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.
DarkNova6 · 5 months ago
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 · 5 months ago
> 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.

Rumudiez · 5 months ago
Yep, I’ve seen this in Swift with a dozen overloads for functions and class initializers to support umpteen similar, but different, types as input. Sloppy schema design reveals itself in combinatorial explosions of type conversions
goranmoomin · 5 months ago
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 · 5 months ago
> 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 · 5 months ago
> 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

whatevaa · 5 months ago
What money did leftpad get?
franciscop · 5 months ago
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/

gourlaysama · 5 months ago
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_ · 5 months ago
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 months ago
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.

sebtron · 5 months ago
> 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 · 5 months ago
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)
mrweasel · 5 months ago
Can you point to a language that's doesn't have poorly designed aspects to it?
quotemstr · 5 months ago
> 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 · 5 months ago
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.

Turskarama · 5 months ago
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 · 5 months ago
Indeed, you've pointed out a second language in the class of "poorly-designed languages"
whatevaa · 5 months ago
C crashes with worst possible error, equivalent of "Something went wrong". Good luck debugging. Only core dumps reveal more information.
MathMonkeyMan · 5 months ago
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 · 5 months ago
Also known as "garbage in - garbage out" approach. That's how Clojure standard library is designed.
pwdisswordfishz · 5 months ago
And C/C++.
chowells · 5 months ago
Now, consider that that the program might be rejected for being incorrect, without being run. Wouldn't that be even better?
MathMonkeyMan · 5 months ago
All other things being equal, yes. Static types work well for constraints on function parameters. "Parse, don't validate" and all that.

Some contracts are more difficult to express, though. The worst kind involve sequences of behavior, e.g. "start() must have been called," or "must have been obtained from a previous call to 'allocate()' on the same object."

Even value parameter constraints can be tricky. "The input must be sorted."

I haven't studied type systems, but I like the idea of values picking up attributes as they pass through functions. Imagine a "sort" function that bestows its output with the "sorted" type attribute. But then what happens when you append to that value? Is it still sorted?

At some point it's convenient to be permissive with the parameter's static type and to constrain the caller with a contract.

moomin · 5 months ago
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.

phito · 5 months ago
I've only seen these kind of libraries in JavaScript. They are a direct result of poor language design.
procaryote · 5 months ago
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 · 5 months ago
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.