HN user

dpryden

230 karma

I'm just this guy, you know?

Posts0
Comments18
View on HN
No posts found.

I was actually expecting a discussion of the PC Magazine humor page, and was disappointed when I clicked through.

It did make me go pick up an old copy of PC Magazine from 1995 from the other side of the room and start leafing through it, though.

I recall from my time in Google Geo years ago that the idea of integrating Search and Maps was a big part of the "New Maps" release that happened around 2014. The rumor I heard was that someone (possibly even Larry himself) wanted to be able to have interactive maps directly on the search results page, so that the navigation from a search query to a map wouldn't involve even a page reload. So the big Maps frontend rewrite actually ended up merging MFE into GWS, the web search frontend server. I recall seeing maps hosted at google.com/maps around that time, but I don't know if that was ever launched fully or if it was just an experiment.

In any case, though, my understanding is that the technical capacity for this has existed for nearly 10 years now, just behind a configuration setting. So it's possible that this change is just a code cleanup. It's also possible that someone is trying to increase the percentage of searches that have location information, that doesn't seem terribly far-fetched either, and I can imagine lots of ways people could try to rationalize it as actually benefiting users. (Whether it actually does benefit users is of course debatable.)

The K&R book is primarily focused on Unix and similar operating systems, and they didn't have threads when the book was written.

The first edition of K&R was published in 1978.

Wikipedia says that OS/360 had a notion of threads as early as 1967, although the distinction between threads and processes wasn't as clearly defined at that time.

In 1978, threading and concurrency was still an area of academic research. There are some great papers from Dijkstra and Lamport from that era, but much of the research at that time considered each concurrent "thread" to be a separate program; that is, each process was conceptually single-threaded, and if you wanted to do multiple things you made multiple processes for them.

The idea of multiple mini-processes inside a single process came later and took a while to stabilize into its modern form. POSIX didn't standardize thread-related APIs until 1995 and Linux didn't get modern POSIX threads support until Linux 2.6 in 2003.

This article is naive to the point of being flat-out wrong, since it makes extremely naive assumptions about how a garbage collector works. This is basically another C++-centric programmer saying that smart pointers work better than the Boehm GC -- which is completely true but also completely misleading.

I'm not saying that GC is always the best choice, but this article gets the most important argument wrong:

1. Updating reference counts is quite expensive. > No, it isn't. It's an atomic increment, perhaps with overflow checks for small integer widths. This is about as minimal as you can get short of nothing at all.

Yes, it is. Even an atomic increment is a write to memory. That is not "about as minimal as you can get short of nothing at all".

Additionally, every modern GC does generational collection, so for the vast majority of objects, the GC literally does "nothing at all". No matter how little work it does, a RC solution has to do O(garbage) work, while a copying GC can do O(not garbage) work.

Now, that's not to say that GC is automatically better. There are trade-offs here. It depends on the workload, the amount of garbage being created, and the ratio of read to write operations.

The article says:

I've already stated I'm not going to do benchmarks. I am aware of two orgs who've already run extensive and far-reaching experiments on this: Apple, for use in their mobile phones, and the Python project.

I can counterpoint that anecdata: Google extensively uses Java in high-performance systems, and invented a new GC-only language (Go) as a replacement for (their uses of) Python.

The right answer is to do benchmarks. Or even better yet, don't worry about this and just write your code! Outside of a vanishingly small number of specialized use cases, by the time GC vs RC becomes relevant in any meaningful way to your performance, you've already succeeded, and now you're dealing with scaling effects.

This is similar to the old debate about the meaning of "frontend" and "backend". I maintain that something cannot be a "frontend" or a "backend" in isolation, it can only be a frontend or a backend to some other system. In a stack with many layers, (nearly) every layer is a frontend to one thing and a backend to another.

That said, I think I tend to use "upstream" in a similar sense to how package maintainers talk about packages: "upstream" is the source (where the thing originally came from) and "downstream" is the sink (where I will send it to). This implies that "upstream" for a request is "downstream" for a response, and vice versa. I don't know if that's necessarily correct usage, but that's how I've done it.

This hints at an (IMO underrated) skill: being able to look at a problem and decomposing it into sub-problems that are solved by known algorithms. Topological sort is a pretty fundamental building block for solving many problems that involve dependencies between components, since those can be modeled as a directed graph.

Knowing how to write a topological sort isn't the key skill here (although, I would argue, it's a good skill to know, and it's probably much simpler than you're imagining). The key skill is knowing that a topological sort will solve the problem, or perhaps it's simply knowing that "finding an order of actions that satisfies these dependency constraints" is called a topological sort.

I have always been under the impression that the goal of leetcode-style interviewing was actually to measure a candidate's ability to recognize what algorithmic tool to use, rather than to measure a candidate's ability to implement an algorithm from scratch. When I've been in the position of interviewing in the past, I've always been more interested in that problem-solving approach than in the actual code.

In my experience, most software jobs are 90% figuring out the problem that needs to be solved and coming up with tactics for solving the problem, and 10% actually implementing those tactics. In that context, investigating the ability of a candidate to analyze problems seems like an excellent interview technique.

I am finding this article and the comments here very surprising. Do a large number of people actually believe that the word "crypto" means exclusively "cryptocurrency"? Does anyone believe it means exclusively that?

To me it seems similar to how "auto" as a noun is generally short for "automobile", but most people are aware that other things can also be called "auto". When a camera says it is "auto focus" I cannot imagine that any normal person would assume that phrase has anything to do with automobiles.

It is incredibly common for the same word to have different meanings in different contexts. I personally have literally never had a conversation about cryptocurrency in which any person used the word "crypto" to mean "cryptocurrency", so I am clearly out of this loop. But if people decide to use it that way as slang in a certain context it certainly doesn't change the meaning of related words, or even mean it's impossible to use a different slang meaning in different contexts.

Overall this seems like a very positive change. However, I wonder how it will affect local development of servers that participate in API flows with public-facing systems.

As an example: imagine I am developing my application locally, at http://localhost:8080/ , and this application supports an OpenID Connect identity flow with an identity provider, https://idp.corp.example . Today I can test the login flow by telling the IDP that localhost:8080 is a valid URL to redirect back to, so that I can click a "login" button in my application, log into the real IDP, and get a token posted back to something like http://localhost:8080/idp-callback . This makes it easy to develop a system locally that also communicates with various backend microservices which require authentication with a common IDP.

I can't imagine that this is a rare scenario: it seems pretty normal to me. But if I understand the proposal, it sounds like the long-term goal is to prevent this kind of environment for local development, and instead force you to either run your dev stack remotely (with a public IP!) or else run your entire IDP stack locally (so that it has a local IP). Neither of those seem like good ideas to me.

Of course, the other option would be to modify local dev servers to accept CORS preflight requests and respond correctly, but I'm always slightly uncomfortable adding code into a local dev stack that would be unsafe if enabled in production. At very least it makes it harder to debug when something inevitably goes wrong with this API flow.

There are probably ways to solve this by introducing more proxies into a local dev stack, but I worry that these kinds of little papercuts will just make development that much harder for microservices-architecture applications, which are already hard enough to develop and debug as it is.

I can't tell if this comment is implying that my comment is unclear, or if you're agreeing with the first line of my comment.

In either case, though, it's an inside joke precisely because it's more relatable to those who are (or were) inside. In particular, I think it would be most funny to someone who was at Google about a decade ago; when I left Google in 2017 things had already changed enough that this didn't ring quite as true for new hires.

That said, GCP is not very representative of what the internal platform looked like circa 2010. (Or even of what the internal platform looks like now, as far as I know.)

It always amazes me how people believe they have a right to retrive data from a website. The HTTP protocol calls it a request for a reason: you are asking for data. The server is allowed to say no, for any reason it likes, even a reason you don't agree with.

This whole field of scraping and anti-bot technology is an arms race: one side gets better at something, the other side gets better at countering it. An arms race benefits no one but the arms dealers.

If we translate this behavior into the real world, it ends up looking like https://xkcd.com/1499

If you are using floating point numbers implemented in hardware, then infinity is absolutely a valid value and one that your code will encounter. This is true regardless of language, as long as the language requires or allows IEEE-754 semantics.

I am not aware of any language (outside of intentionally-minimalist esolangs) that doesn't support floating point numbers. In some languages (like JavaScript) that's the only kind of number you get.

I'd like to chime in and say that I also like C but really dislike Go.

C is simple, to what today is nearly an absurd degree, but it at least integrates well with an enormous ecosystem of existing tooling and libraries. Go, by contrast, feels the need to re-invent every possible wheel, often with a seemingly intentional effort to be different just because.

C ABIs are rock solid on every platform. Any language or tooling in the world can load functions from a C library. By contrast, try calling Go from another language. You will quickly give up and use network protocols or subprocesses instead.

The converse is true too: on major platforms, the C library defines the operating system more than the kernel does. (I mean, in a Unix sytem, the entire libc is documented in the man pages. Documentation for writing C is literally built into the operating system.) By contrast, Go decides to avoid the standard platform ways of calling every operating system function, and instead implements syscalls directly into the kernel, basically just because Go hates interoperating with anything that's not Go.

C is a good language not because it is a good language, but because it is a good ecosystem. Unix and C are closely intertwined. (Even Win32 and C are pretty closely intertwined!) I could imagine a Go-based Plan9-like system -- if that existed, and had decades of engineering behind it, I could see the argument being made that Go's ecosystem compares to C's. But while Go may aspire to that, it isn't anywhere near there yet.

And when you start comparing Go with other languages on the basis of language features, I think Go fails laughably more often than not. (I'll give you "C++ is a clumsy mess", though.) But compare Go with Rust, with C#, with Haskell -- for that matter, compare Go with Java and I think Go is a less powerful, less useful language to write real code in.

If I want a lightweight, quick-and-dirty program, I'll use Python. If I want to write a reusable library that gets plugged into other software, I'd go with C (or at least write the interop layer in C). If I want a high-performance server, these days I would probably go with Rust. If I want a pragmatic, well-rounded language to write a large project in, I think C# would probably be my first choice.

What space does that leave for Go? Outside of microservices (where I contend that Rust is a much better choice, and there are lots of other options), Go's niche seems to be command-line tools. I sort of understand that, because Go's static linking makes redistributing binaries relatively easy (although Go by no means has a monopoly on that). But in the space of command-line tools, Go's argument handling is frankly bizarre: it looks at decades of precedent about what users expect from argument syntax and behavior and decides to throw it all away and reinvent the wheel.

I guess that means this comment (rant?) has ended up where it started: Go defies convention, basically "just because". By contrast, C defines convention. "If you like C, you will love Go"? Nope, not at all.

"PC LOAD LETTER" was a very common message on the old HP LaserJet Series II printers. This cryptic message on the tiny LCD display was short for:

PC: "Paper Cassette" Load: "out of paper, please load more" Letter: "US Letter size (8.5"x11")"

In some offices you'd be equally likely to see similar codes like "PC LOAD A4" or "PC LOAD LEGAL".

The LaserJet Series II and III didn't have paper drawers, but rather paper cassettes, which you could load with a stack of paper (maybe a few hundred sheets? less than half a ream IIRC), and then swap in and out of the machine as a unit.

Some models (I think it might have been an add-on peripheral for the Series II?) had two cassette slots, so you could load two supplies of paper at the same time. Either you could load the same size twice, in which case it would perform like a backup: if you ran out of paper in the first cassette, it would switch to the second cassette automatically but start flashing a light to tell you that it was time to reload the first one. Or you could load two different sizes, and it would select the right cassette to draw from based on the size of the document being printed.

The Series II didn't have a way to measure the size of the paper, so each cassette was designed for only one paper size. There was an interface where the cassette plugged into the printer which indicated which size paper it contained.

Due to the design of the paper cassette, it was very hard to tell from the outside how much paper was left. There was a tiny window but it was nearly opaque. IIRC later versions of the cassette improved this.

So if you had a model with only one cassette (which was fairly common, I think) and you loaded a "US Letter" size cassette, and it ran out of paper, the printer would refuse to print anymore until you gave it more paper. In the meantime the tiny LCD screen would flash "PC LOAD LETTER".

The printer was a workhorse and I can testify that many offices in my area were using 1980s-era Series II printers daily in 1999, when I had a part-time IT job that among other things involved doing maintenance on said printers. Replacing the rubber paper pickup roller and clearing paper jams out of the fusing unit were probably the two most common trouble cases, IIRC.

My first reaction to the page was: how do I know who wrote these? How do I know whether they are right or wrong? How do I correct ones that I can see are incorrect (or at least misleading)?

I don't see any "about this site", "who created this site", etc. I don't see any information about who authored a particular snippet. Perhaps more information is available if you log in? But it seems like it should be possible to evaluate the trustworthiness of a site _before_ sharing information with it, so I don't feel comfortable interacting with the site as it is.

I think much of the difference here depends on your workflow. Where do you run tests? How do you manage build infrastructure? What is your process for code review? What is your process for validation and testing of shared branches? How do you manage releases?

In my opinion, squash merges make the most sense when the following are true:

1. Developers cannot push directly to shared branches; all commits into shared branches are done via pull requests with mandatory code review.

2. Shared branches must build and pass tests at every commit.

3. The team builds features incrementally and uses the "Branch by Abstraction" approach (feature flags/experiments) to ensure functionality can be merged before it is ready to be enabled in production.

4. Changes that merge into the shared ("trunk") branch are released frequently.

Now, you may argue against some of those practices, and if you end up in a different place on one of those, squashes may not make sense to you. But then the actual difference of opinion is elsewhere. It's not really about squashing, it's about how much code is a reasonable granularity to code review at one time.

If you can keep the code review cycle tight, then a pull request branch basically becomes almost like a virtual pair-programming session. There may be some back and forth as the author and reviewer settle on a final form of the commit. But I don't see the commits that happen along the way as valuable in the least. (In my experience, the vast majority of intermediate commits are things like "fix typo", "make linter happy", "rename this function because code reviewer pointed out it was named inconsistently", etc.) Instead, my mental model is that a pull request is like a mutable commit: you keep mutating it until it's the commit you want, and then you merge it. Since my mental model is that a pull request is a kind of commit, it makes sense that when it merges in, it does so as a single commit.

Again, I'm not saying that this is the only valid way to work. But neither is it an invalid way to work.

As a meta-comment: When you see someone else making a technical decision that doesn't make any sense to you, rather than instantly assuming that they are a clueless incompetent, it's usually more instructive to assume that the decision does make sense to them, and to try to figure out what about their circumstance is different from yours to make that the case.

I'm confused about how the documentation recommends using a Kubernetes operator to manage OS updates. That seems weird and backwards to me. I would rather see an immutable OS AMI in an auto-scaled group, and just replace the node instance whenever there is an update.

I can see a place for managing OS updates on an instance, but that seems more like "pets" than "cattle"... and I've always treated Kubernetes nodes like cattle, not pets. Isn't that the most common approach anyway?