Hacker News new | ask | show | jobs
by gary_bernhardt 4323 days ago
Mutation isn't necessary to demonstrate the problem. Consider three libraries: first, there's a basic, widely-used datetime library. There's also a timezone library depending on datetime 1.0 and a dateformat library depending on datetime 2.0.

  # Get the current time in the PST time zone (returns a 1.0 object)
  now = timezone.now_in_zone("PST")
  # Format the date for display (accepts a 1.0 object but depends on 2.0)
  formatted = dateformat.format(now)
Now the problem: imagine that datetime 2.0 switched from one-indexed months (1 is January) to zero-indexed months (1 is February). The timezone library depends on datetime 1.0, so it used "1" to indicate January, giving me a datetime value with month=1. The dateformat library depends on datetime 2.0, so it incorrectly interprets that month=1 as February. All of my January dates will now be incorrectly formatted as February.

(Switching month representation is a rather drastic example, but it's also clear. Substitute a more subtle data format change if you'd like.)

There's no mutation here, and the dependency graph is trivial. I just received a datetime from one library and passed it to another library.

It's possible that I'm missing something, but I've been asking Node users about this for a couple years and I usually get blank stares. I also have no horse in this particular race. Vendoring seems like a great idea to me, but I fear the uncertainty of this version mismatch situation.

1 comments

The problem is here: "(accepts a 1.0 object but depends on 2.0)"

This case actually has nothing to do with nested dependencies. Your time zone lib is returning a datum with one type, which you're passing to a formatting lib that expects a datum with another type. This can happen if your libs have dependencies that are totally different libraries instead of the same library with different versions. It can also happen if your libs have no dependencies at all. This is not an issue of dependencies but of you not understanding your libs' APIs.

In most languages, a type mismatch would always correspond directly to a type name mismatch. In e.g. Python (since it has a clear module system), if I know that f() returns a datetime.datetime, and I know that g(t) takes a datetime.datetime, then I know that they will compose. (Modulo bugs of other types, of course.)

When using NPM, I don't have that guarantee. The docs for these libraries could clearly state that they'll integrate around the datetime type, but that may be false in practice. And I'll only know that ahead of time if (1) I know that they both use datetime internally, (2) I know exactly the versions of datetime that they use, and (3) I know exactly how datetime changed in between those versions. With non-vendoring package management, I don't have to know any of these things.

You've correctly identified a guarantee that you don't have with npm. In practice, anecdotally, I've never run into this issue, while I have many times and with much pain dealt with conflicting deep dependencies using bower and bundler. Which may explain the blank stares. It's a tradeoff I am personally happy to make.