That's the idea! "Allow" the user to install any apps they choose. (I put "allow" in quotes, to emphasize how bizarre it is that a few platform vendors get to decide what all of humanity is "allowed" to do with their computing.)
HN user
beautron
This is sort of the defining mechanic of these games in my memory. The first thing that pops into my head when I think of Last Ninja is aligning and realigning myself, and squatting, awkwardly and repeatedly (just like a real ninja, lol), until that satisfying new item icon appears. Perhaps surprisingly, these are very fond memories.
This mechanic is augmented by not even always knowing which graphics in the environment can be picked up, or by invisible items that are inside boxes or otherwise out of sight (I think LN2 had something in a bathroom? You have to position yourself in the doorway and do a squat of faith).
The other core memory is the spots that require a similarly awkward precision while jumping. These are worse, because each failure loses you one of your limited lives. The combat is also finicky. I remember if you come into a fight misaligned, your opponent might quickly drain your energy while you fail to get a hit in.
At the time, it seemed appropriate to me that it required such a difficult precision to be a ninja. I was also a kid, who approached every game non-critically, assuming each game was exactly as it was meant to be. Thus I absolutely loved it, lol.
One problem with the /italics/ form is that it's not convenient when writing about filesystem paths (though I do like its visual indication of slanting).
Perhaps more proof of work is necessary, but it makes me sad.
I still remember creating my HN account. It stands out in my memory, because it was the smoothest, simplest, easiest, and quickest account creation of my life.
I had lurked here for around a decade before finally creating an account. Any urge to participate was thwarted by my resistance toward creating accounts (I just hate account creation for some reason). But HN's account creation process was a breath of fresh air. "You mean it can be this easy? Why isn't it this easy everywhere? If I had known how simple it was, I would have created an HN account years earlier, lol."
It was especially stunning to me, because I think the discourse on HN is generally of a higher quality than most other sites (which I wouldn't naturally associate with such an easy account creation process).
It's my only fond memory of account creation (along with maybe when I created an account on America-Online back in the 90s, since that was my first ever account and it was all so novel). Just a few quick seconds, and then I'm already commenting on HN. It was beautiful. I remain delighted.
I prefer the perspective that a computer program is akin to a mathematical constant. This was true in the old days. A program I wrote in C64 BASIC, way back in 1980s, should still work precisely the same today (even on one of the freshly-manufactured Commodore 64 Ultimates).
You've honed right in on what's changed since the old days: Platform vendors (such as Apple) now continuously inject instability into everything.
You might argue that developments such as "changing processor architectures" justify such breaks from stability (though I myself have qualms with one of the richest companies in the world having a general policy of "cutting support and moving on"). But I would point out that Apple (and other vendors) create instability far beyond such potentially-justifiable examples.
To me, it appears as if Apple actively works to foster this modern "software is never finished" culture. This is seen perhaps most clearly by they way they remove apps from their iOS store if they haven't been updated recently (even as little as 2 years!): https://daringfireball.net/linked/2022/04/27/apple-older-gam...
Shouldn't we be demanding stability from our platforms? Isn't the concept of "finished software" a beautiful one? Imagine if you could write a useful program once, leave it alone for 40 years, and then come back to it, and find that it's still just as useful. Isn't this one of the most valuable potential benefits of software as a premise? Are the things we're trading this for worth it?
Not a lot of companies allow disabling their garbage, but FF does. > Can't we be happy with this nice switch?
I want my tools to keep working the way they have been working. I don't want to be paranoid that "garbage" (as you put it), or any other controversial changes, are going to be slipped into my tools while I'm not looking.
The typical Go story is to use a bunch of auto generation, so a small change quickly blows up as all of the auto generate code is checked into git. Like easily a 20x blowup.
Why do you think the typical Go story is to use a bunch of auto generation? This does not match my experience with the language at all. Most Go projects I've worked on, or looked at, have used little or no code generation.
I'm sure there are projects out there with a "bunch" of it, but I don't think they are "typical".
But sometimes it is useful to return both a value and a non-nil error. There might be partial results that you can still do things with despite hitting an error. Or the result value might be information that is useful with or without an error (like how Go's ubiquitous io.Writer interface returns the number of bytes written along with any error encountered).
I appreciate that Go tends to avoid making limiting assumptions about what I might want to do with it (such as assuming I don't want to return a value whenever I return a non-nil error). I like that Go has simple, flexible primitives that I can assemble how I want.
I share your unpopular opinion.
While I understand that having identical UI elements across apps aids in discoverability, I just love it so much when an app has its own bespoke interface that was clearly made with love.
Like you, it might be my love of games that has given me this preference. Would StarCraft II have a better UX if its menus used the standard Windows widgets where applicable? I think certainly not. And I think the same can be true for many non-game apps.
My perception is that there's always been some dissonance between loving tech intrinsically for itself vs. the Silicon Valley venture capitalist business model. Conflating tech with its dominant business model enables the paradox where a person deeply in love with tech can also be anti-tech.
Going from a project with 1,000 to 1,000,000 lines of code is a tiny leap compared to going from 0 to 1000.
Are you sure the leap is tiny? It's a much easier problem to get only 1,000 lines of code to be correct, because the lines only have to be consistent with each other.
When there are many and diverse data formats that meet that standard, it seems perverse to use the word "easy" to talk about empirically discovering the quirks in various undocumented dialects and writing custom logic to accommodate them.
But the premise of CSV is so simple, that there are only four quirks to empirically discover: cell delimiter, row delimiter, quote, escaped-quote.
I think it's "easy" to peek at the file and say, "Oh, they use semicolon cell delimiters."
And it's likewise "easy" to write the "custom logic", which is about as simple as parsing something directly from a text stream gets. I typically have to stop and think a minute about the quoting, but it's not that bad.
If a programmer is practiced at parsing from a text stream (a powerful, general skill that is worth exercising), than I think it is reasonable to think they might find parsing CSV by hand to be easier and quicker than parsing JSON (etc.) with a library.
If somebody give you a JSON file that isn't valid JSON, you tell them it isn't valid, and they say "oh, sorry" and give you a new one. That's the standard for "easy."
But it isn't that reliably easy with JSON. Sometimes I have clients give me data that I just have to work with, as-is. Maybe it was invalid JSON spat out by some programmer or tool long ago. Maybe it's just from a different department than my contact, which might delay things for days before the bureaucracy gets me a (hopefully) valid JSON.
I consider CSV's level of "easy" more reliable.
And even valid JSON can be less easy. I've had experiences where writing the high-level parsing for some JSON file, in terms of a JSON library, was less easy and more time-consuming than writing a custom CSV parser.
Subjectively, I think programming a CSV parser from basic programming primitives is just more fun and appealing than programming in terms of a JSON library or XML library. And I find the CSV code is often simpler and quicker to write.
I also love CSV for its simplicity. A key part of that love is that it comes from the perspective of me as a programmer.
Many of the criticisms of CSV I'm reading here boil down to something like: CSV has no authoritative standard, and everyone implements it differently, which makes it bad as a data interchange format.
I agree with those criticisms when I imagine them from the perspective of a user who is not also a programmer. If this user exports a CSV from one program, and then tries to load the CSV into a different program, but it fails, then what good is CSV to them?
But from the perspective of a programmer, CSV is great. If a client gives me data to load into some app I'm building for them, then I am very happy when it is in a CSV format, because I know I can quickly write a parser, not by reading some spec, but by looking at the actual CSV file.
Parsing CSV is quick and fun if you only care about parsing one specific file. And that's the key: It's so quick and fun, that it enables you to just parse anew each time you have to deal with some CSV file. It just doesn't take very long to look at the file, write a row-processing loop, and debug it against the file.
The beauty of CSV isn't that it's easy to write a General CSV Parser that parses every CSV file in the wild, but rather that its easy to write specific CSV parsers on the spot.
Going back to our non-programmer user's problem, and revisiting it as a programmer, the situation is now different. If I, a programmer, export a CSV file from one program, and it fails to import into some other program, then as long as I have an example of the CSV format the importing program wants, I can quickly write a translator program to convert between the formats.
There's something so appealing about to me about simple-to-parse-by-hand data formats. They are very empowering to a programmer.
I love that the Go project takes compatibility so seriously. And I think taking Hyrum's Law into account is necessary, if what you're serious about is compatibility itself.
Being serious about compatibility allows the concept of a piece of software being finished. If I finished writing a book twelve years ago, you could still read it today. But if I finished writing a piece of software twelve years ago, could you still build and run it today? Without having to fix anything? Without having to fix lots of things?
Sure, but now that there's a "correct" way to do this, you don't get to complain that the hacky thing you did needs to keep being supported.
But that's the whole point and beauty of Go's compatibility promise. Once you finish getting something working, you finished getting it working. It works.
What I don't want, is for my programming platform to suddenly say that the way I got the thing working is no longer supported. I am no longer finished getting it working. I will never be finished getting it working.
Go is proving that a world with permanently working software is possible (vs a world with software that breaks over time).
This matches my experience. I think I have a 25 hour circadian rhythm, which has me always wanting to stay up one hour later than the night before.
I think the experience of building something atop a framework should absolutely have bearing on how to build the underlying framework.
And it doesn't really color the function because you can trivially make it sync again.
Yes, but this goes both ways: You can trivially make the sync function async (assuming it's documented as safe for concurrent use).
So I would argue that the sync API design is simpler and more natural. Callers can easily set up their own goroutine and channels around the function call if they need or want that. But if they don't need or want that, everything is simpler and they don't even need to think about channels.
Wow, this is wild! Maintaining perfect justification solely through word choice is... quite a writing constraint, lol. I don't think I've seen this done before.
Didn't Unity and Epic have people working directly on the Vulkan specification? I remember reading a comment (maybe here!), way back during Vulkan's original release, where someone was screaming about how Vulkan was a conspiracy to make us dependent on giant, multi million dollar game engines.
I don't know about any conspiring, but I've thought about that comment often, because the result appears the same: The barrier to in-house game engine development has been risen further.
Common Lisp's string support certainly feels like it comes from a world where allocating memory is expensive, and something the programmer should be aware of, and have the option to reuse buffers instead of allocating additional memory...
We are still in that world. I do game programming, and am convinced that if a game programmer isn't aware of their memory allocations, then their game will drop frames. This is true today, even at 60hz (and things are moving towards 144hz, 240hz, and beyond). Reusing buffers is essential.
I think that being aware of when and where your memory comes from is the foundation of performance. The discipline of handling memory carefully tends to naturally lead toward making more performant decisions on everything else.
This rings true to me, perhaps because I struggle with this one.
I used to think I was destined for success, because I'm good and creative at many things, and am curious and can teach myself things.
But: I expected the system to integrate me. I realize now that, unless you're lucky, it's up to you to integrate yourself into the system.
It's a bummer to be underpracticed at this, and to be integrating suspiciously late. Feeling your potential, your ability to create great value for the system (while hopefully inspiring the system to also take good care of you). But instead only tapping a small fraction of your capability, because you haven't managed to link yourself in right.
In general, when doing low level optimization of Go code, avoiding allocations is the biggest bang for your buck.
I have found this to be the truth. I'm making a game in Go, and have learned that a smooth framerate depends on programming with allocation awareness. Always know where and when your memory is coming from.
I'm sure Magit is lovely. But I can't resist sharing the story of my bad experience with it over a decade ago (which has left me scared away).
It was maybe early 2012, and I was excited to try Magit. I got it set up, and called 'M-x magit-init' from a source file I was editing. My understanding was that this would create a new git repo in that source file's directory, ending up with something like "/home/beautron/myproject/.git".
But something else happened. The git repo was put here instead: "/home/beautron/myproject/~/myproject/.git". Note the peculiar "~" directory, created inside the project directory.
Huh. Weird. Well, let's get rid of this mess and try again. I went to the project directory in my bash shell, typed "rm -r ~", and hit enter. Somewhere between my mind firing the signal to hit enter, and enter actually being hit, I realized with horror what this command would do. But it was too late to cancel the brain signal.
I didn't lose everything, because I had not typed something worse like "rm -rf ~", and somewhere in my home directory tree was a read-only file. So the command only deleted so far as that file, and then paused to ask for confirmation.
I estimated I lost about half of everything (the first half of the alphabet was gone from my home directory). The most frustrating thing was not even being sure what all I had lost. On the plus side, this experience improved my regimen around backups.
As I was trying to salvage the wreck of my system, I had a separate laptop out on the side, where I was trying to get some help, or maybe just some sympathy, from the #archlinux irc channel on freenode. But the two people who responded to me on the channel were very snarky to me. I felt they thought I was clearly an idiot for having run that command.
The irc people refused to believe that Magit created the "~" directory. They were convinced I had done that myself, with some other series of stupid commands. (If you had to guess the source of weird "~", who would you choose: the established Magit project, or the guy who just deleted half his home directory?)
But a short time later I was vindicated! From Magit's github issue 383, Feb 29, 2012:
So if you're editing "~/Temp/foobar/boo.txt" and call "M-x magit-init" which defaults to "~/Temp/foobar", instead of creating a git repo in "/Users/jimeh/Temp/foobar" it creates it in "/Users/jimeh/Temp/foobar/~/Temp/foobar".
Source: https://github.com/magit/magit/issues/383
It was a long night (and I had to leave on a trip the next morning). Now it's fun memory, perhaps with a number of lessons in it.
This is a great analysis that gave me a lot to think about.
I've always loved the way Go's packages work. With care, I can organize large codebases that end up feeling so clean and nice to deal with.
I usually get to pick all my own package names, so the issues you bring up are manageable for me. But you explained them well, and I can imagine the frustration of inheriting public API packages that were not carefully designed.
And this one is a perennial little naming hiccup:
Who among us hasn't wanted to shadow the name `url` or `path`?
I still prefer Go's packages to other languages' ways of code organization, but I think you convinced me that giving packages their own separator (and not sharing '.') might have been a better design. Though strangely, as you mentioned, any other separator is uglier.
I feel this strongly. Physical books make a deeper impression in my memory. There are all sorts of particularities of how the book was bound: it's size, it's smell, the texture of the paper, etc. It's like the book exists in its own unique space (vs all digital books existing in the single shared space of my computer/reader). I think a book's reading environment loses some richness when all these subtle little details are homogenized.
Yes, but even with that interpretation, they claimed something much stronger:
The encoded value can change any time you re-compile your program
Any value (not just enums) can change any time you re-compile your program, if some programmer goes in and messes it up.
The real, much softer criticism would be that Go requires its programmer's to understand the potential consequences of inserting or shuffling enum values (where iota is involved). It's a much weaker case against iota than what they stated.
I love Go's lightweight, somewhat implicit style of doing enums.
You just declare an int type, and then a list of constants of that type.
People are complaining about 'iota' here, but I think it's slick and great. It combines so nicely with eliding types and values from subsequent const declarations:
type MyEnum int
const (
Value1 MyEnum = iota
Value2
Value3
...
)
Nice and simple. Most of the syntax is just the enum value identifiers. And it works well for bit flags too: type MyFlags int
const (
Flag1 MyFlags = 1 << iota
Flag2
Flag3
...
)
Most of the above syntax isn't specific to enums (so you're already getting a lot of other things from it). The only enum-specific syntax is iota and the eliding type/value rule.People seem to want their languages to have all sorts of guardrails, but I find many of these cumbersome. Go gives me the one enum guardrail I care about: The enums are different types, so I can't use a MyEnum as a MyFlag, or vice versa.
I've worked on giant Go codebases, with Go-style enums all over the place, and the lack of compiler-enforced exhaustive enum switches just hasn't been a problem. And it's nice to be able to use non-exhaustive switches, when you want them. Go is simple and flexible here.
The article criticizes Go incorrectly with statements such as these:
This also means any function that uses these or a struct that contains these can also just take an int value.
Anywhere that accepts an Operation Enum type will just as happily accept an int.
This is just not true. Here's an example you can run: https://go.dev/play/p/8VGufuxgK6b
The above example tries to assign an int variable to a MyEnum variable, and gives the following error: "cannot use myInt (variable of type int) as MyEnum value in variable declaration"
This error directly contradicts what is claimed in the article. Perhaps they mean that MyEnum will accept an integer literal, in which case I would argue that a guardrail here is silly, because again the problem just doesn't really come up in practice. Regardless, the author is not being very precise or clear in their thinking.
But Twitter doesn't address many of the downsides of social media that were listed, or many of the upsides of RSS that were listed.
I agree that weighted keys are preferable, but it's not about "building finger strength," but rather about building finger coordination. Your fingers are already plenty strong enough for weighted keys (especially once you learn how to take advantage of gravity in your technique).
But weighted keys are crucial for building coordination. The resistance of the keys helps you calibrate the connection between your touch and the sound produced. You want both kinesthetic and aural feedback.