HN user

mostlylurks

483 karma
Posts0
Comments165
View on HN
No posts found.

Not represented directly in memory in its raw form right where the value is placed, but rather stored somewhere else (usually the heap) / in an opaque manner and accessed via an indirection, such as a pointer / reference. Often involved in making things polymorphic, because different types usually have different representations in memory even if they implement the same interface, so they need to be boxed.

We can't do that, however, because Prolog doesn't support unification of functors. We could patch that if we started referring to function application with more elaborate syntax by parsing "sin(exp(x))" as application(sin, application(exp, x)) so that we could unify on sin, but this quickly obscures the syntax tree and removes the delightfully direct nature of DCGs.

Would the =../2 predicate help? It allows for you to unify functors without wrapping them in something like your application(exp, x) example. Not sure how it would interact with DCGs, though - never took more than a superficial glance at them.

Why is an extra element required, and why is it <font> of all things?

I don't know, but perhaps due to the fact that due to the CJK unification in unicode, rendering Chinese or Japanese without explicitly setting a font designed for that particular language can output incorrect characters (of the other language, which are considered "the same character" despite being different). Thus, a translation tool would have to explicitly set a font in order to display these languages correctly in a reliable manner, because the surrounding context certainly cannot be assumed to have the appropriate font. And I could easily imagine that someone would choose to keep the same code path for all languages instead of branching for this particular case, resulting in a <font> even for languages other than those two.

The presented trichotomy between no moderation, moderation, and federated moderation is false.

Moderation can also be accomplished via a user-level web-of-trust system, where each user can choose who to trust as a moderator, and this trust can propagate recursively to the people trusted by the people you trust, and at each level (even when manually choosing people to trust) this trust can be fuzzy (not full trust vs no trust, but potentially something in between those two), and rapidly decreasing the more distant you get from those you've manually chosen to trust. To solve the issues of spam, censorship, and convenience simultaneously, you simply assign to users some moderators on the trust list by default and allow users to opt out of that trust.

This approach is also applicable in the same manner to the similar problem of curation (i.e. choosing what to highlight instead of what to hide), where the same four approaches are also applicable with largely the same pros and cons.

Techies just love to build and configure things to their liking.

I don't, and I don't believe I'm even in the minority in that regard. What you are referencing is a stereotype that may reflect a minority of so called "techies", but even those are almost certainly only interested in building and configuring things within some specific field of interest, but still want everything outside of that to "just work".

The rest of us have things to do, and would just rather buy the functionality they need in a ready-to-go and easy-to-use form.

A false dichotomy that is often repeated, but incorrect nevertheless, for it is the proprietary ecosystem that keeps breaking things over and over again, changing UIs, features, and even very basic settings you've set, dropping support for various things (apps, devices, etc.) you might still use and that still work fine.

My linux installations have made everything work directly out of the box (unlike some properietary systems where you have to install things and fiddle with settings to make things work) and have stayed almost identical in terms of their UI and already-existing features for a decade now (and could have for quite a bit longer if I had adopted linux earlier). No properietary system could come even close to this level of "just works"-ness (though apple probably gets far closer than the others).

Developer-users are real users. A tool (such as a piece of software) is not just a toy just because it's targeted towards users that actually have the skillset to make proper use of it. In fact, quite the opposite; the most useful tools (in any domain, not just software) are often quite inaccessible to those without the prerequisite expertise. Commercial products may be heavily incentivized to make themselves appeal to even the most inexperienced users, and obtain a much wider userbase as a result, but how many of those users actually do something useful with that, and is the proportion of such users high enough that it would make sense for FOSS developers to target them, instead of their existing more reliably competent userbase?

You don't, because JSX is not required for react. This isn't even just a theoretical point, but something I've actually done in the past several times; it's very convenient to just try something out by throwing react into a script tag and then just writing a small prototype or something without utilizing JSX and thus avoiding the need to set up a build system and everything else. I would still use JSX for larger projects, of course, but it is mostly just a small quality-of-life improvement, and I'd be using react even if JSX didn't exist, as the actually important parts of the library are not about JSX.

I don't find it more difficult to use or remember commands for than remembering how to accomplish similar tasks in some GUI (especially if that GUI is emacs). And unlike most GUIs (emacs may be an exception), I can trust that my knowledge of the git CLI won't become out of date when my GUI tool inevitably undergoes a UI redesign of some sort.

But more importantly, the CLI allows my typical workflow where I chain together a bunch of git (and other) commands in a row, allowing me to just type in, for instance, several different commits, their messages, and what files should go into each in one go without having to break my concentration by having to move around in some GUI between commits. Sprinkle in some stash manipulation and interactive rebases, compilation, and unit testing, and you'll really start to see how the CLI allows you to offload some of your working memory to your invocation in a way that a GUI just can't.

With commit messages, there is a very clear starting point: the commit message for the commit that last touched the line of code you're looking at with git blame, which is my standard solution for finding out the reasoning behind any piece of code I don't quite understand. Only works for projects that don't destroy their history with squashes or otherwise write uninsightful commit messages (e.g. "fix bug").

It's also what I, as a user, want. I don't want some middle-man corporate "platform" (of which there are very few to actually select from to suit your personal tastes) inserting their vision of what UIs should look like into interactions between me and the service I'm connecting to. I want my platform to be as invisible as possible and interchangeable with other platforms. The more things stay the same when switching between platforms, the better.

Cars suck, man 3 years ago

I actually have, but with the caveat that you have to know how to place the dishes in the thing such that they don't block the flow of water. Not a burden once you know how to do it, but might initially take some time to figure it out, if just looking at the thing doesn't make it obvious.

The most powerful abstractions are typically not free. The rate of performance improvement from improvements in hardware has slowed down and might continue slowing down. Thus, the pressure to learn what happens behind various abstractions might actually increase, if advantageous performance improvements can primarily be obtained by peeling back the layers of abstraction at the cost of some development efficiency, since you can't just wait a couple of years for hardware to develop to a point where it can handle your inefficient abstraction anymore.

“Web development shouldn’t need a build step”

A build step is a huge barrier that makes authoring your own websites require significantly more expretise than it otherwise would. It thus makes web development less accessible, and puts anything even a little bit complicated out of reach of anyone who isn't already an experienced web developer or exceptionally dedicated. It also discourages the slow development of some pet website of a not-primarily-a-web-developer-by-trade into something more featureful, since expanding beyond the point where you can still reasonably avoid a build step suddenly requires acquiring a lot more exprertise and expending a lot of effort all in one go, instead of just being a smooth expansion into a more featureful project.

The simpler it is to make your own website without having extensive web development experience, the more people will be able to have their own website instead of being directed to the endless array of corporate silos like social media (which has largely replaced personal websites) and corporate middlemen (which have largely replaced in-house commercial websites for smaller actors) that take care of the burden of making your own website for you, with some rather obvious downsides.

If you're doing proper atomic commits, as most people critiquing squashing probably are, the overhead of making separate PRs for each would be ludicrous. It depends heavily on the situation, but in favorable conditions (greenfield development of some simple CRUD app, for instance) you can easily produce dozens of clean, atomic commits a day. As part of the same PR, those take almost no time at all to review. Put into separate PRs, you'd be wasting a lot of time and effort both on the reviewer's and reviewee's side.

How do people with workflows that don't do any squashing do code review?

Going through the commits one-by-one or just looking at the entire diff both work just fine in most cases. In the former case, the commit messages (even if short one-liners) actually help understand the story of how and why the changes ended up taking the shape they did, so it's usually actually easier than just reading through one big diff.

To me it always seems the main consideration deciding on commit size is about being considerate of the reviewer. Don't want to harass them with huge commits but also don't want to send barrages of tiny uncontextualized changes.

Commits should be atomic. Their size is irrelevant to that consideration. An atomic change may be one character, or twenty thousand lines (if those changes constitute an atomic (= singular and indivisible) change). This usually doesn't result in a barrage of tiny changes that are difficult to understand on their own, but even if it did, commit messages, the sequence of commits, the full diff of the MR/PR, as well as the attached information on the issue tracker you use (which you presumably have if you're doing code review) all provide more than enough context.

OOP is about building sub programs with their own state and behaviours, yet implementing a common interface so they can still effectively communicate to each other.

This doesn't seem like a sufficient definition, as closures are also sub programs with their own state and behaviors that implement a common interface (the function signature), and you'd have to stretch the definition of OOP to the point of meaninglessness if that would be sufficient to qualify as OOP.

Even polymorphism that is resolved at run-time is not specific to OOP. In fact, it's used quite extensively outside of OOP, including in both functional and procedural programming. Even ignoring the types of dynamic polymorphism present in other paradigms that might be called "OOP-inspired", there are commonplace patterns such as the use of closures that let you achieve run-time polymorphism and data encapsulation.

Yes, but the fact that react uses the DOM is because that is what the very restricted platform gives you out of the box, and opting for other options would have several disadvantages that are purely the result of those other options not being directly provided by the platform itself. A reactive UI library (like react) could very easily be implemented on top of other UI paradigms as well, including decidedly non-OOP ones. There's nothing special about object oriented UI systems that motivates reactive UI libraries to use them except for the fact that that's what the underlying platform provides because that was what was in fashion when the platform was taking shape.

There is no need to accommodate for future usage. Most of the issues resulting from abstractions actually come from people treating abstractions as sacred, trying to adapt abstractions used by some piece of code to fit new use cases, when they should be treating abstractions as disposable instead, swapping them out and creating new ones instead of altering existing ones. If the abstraction that you come up with based on the first few examples is not suitable for new use cases, that's completely fine.

As far as generality is concerned, it's not the number of examples that matters. Once you learn to distinguish whether your duplicated code actually duplicates some piece of knowledge or just happens to resemble another piece of code by happenstance, you stop having to rely on an unreliable magic number as your limit on how many instances of duplication to allow before abstracting something. And that skill probably correlates strongly with the understanding that abstractions are not just macros that make it more convenient to repeat some chunk of code, they actually represent knowledge, and thus their primary use is not to eliminate duplication, but to encode an understanding of a problem domain into the code.

It leads to over-abstracted, hard-to-change code that runs badly and is much harder to understand, generally speaking.

Abstractions, even bad abstractions, are far easier to deal with than unabstracted code. Abstractions are cheap and disposable. You're not married to them. They only seem hard to change if you're stuck on the mindset that to implement a change to a piece of code using an abstraction, that you have to change that abstraction, which is incorrect. If an abstraction no longer fits, you should more often than not stop using it in that part of the code rather than altering the abstraction to fit the new use case. They are also easier to understand by virtue of communicating their intent, unlike unabstracted code. It takes a lot of effort, on the other hand, to read through and understand a chunk of unabstracted code, and even more to change it or write a slightly differing variant of it for a new use case, and even more to extract any kind of abstraction from a bloated unabstracted mess. With this being the case, it always makes sense to eagerly abstract if you can, even if your abstractions are bad, because the result will still be more maintainable than unabstracted code.

Bad abstractions are a time sink

Yes, but contrary to the general perception, I fend them to be far less of a time sink than duplicated code. It takes almost no time at all to bypass or rework a bad abstraction, but code duplication necessitates either a literal multiplication of your efforts wherever you need to add features to duplicated code, or to refactor the duplication into a non-duplicated form, which also takes a lot longer than dealing with a bad abstraction. Abstractions are cheap and disposable, even if they're bad. Duplication is expensive and hard to get rid of.

With semantic versioning, it's easy to go back and provide a security update (for instance) for an earlier version of a library (which matters if the newer version is API compatible or has different dependencies or a different license or has stopped supporting the platform the user is on). If you try to do the same with date versioning, your versioning suddenly because very misleading and confusing.

Preconditions and postconditions seem like the wrong way to go about solving the issue they try to solve. They are essentially a secondary type system that tries to express information not expressed by the primary type system in a way that is awkwardly disconnected from the primary type system. You could instead just implement the functionality needed to incorporate that information into the primary type system. In practice this would result in an implementation of dependent types, which is fine.

Even if you were to label it as such, how would that prove the original point? Labeling a trait as a medical condition does not make it vanish. If that alleged 3-5% of the population does not share the trait that you claim is universal, then that trait is simply not universal, regardless of what status or judgement you decide to assign to whatever commonality those alleged 3-5% of the population have. The two matters are completely orthogonal.

Cover art is not unconnected to the work itself. It correlates fairly strongly with the stylistic choices made in a book, though that correlation becomes more tenuous the more distance you add between the author and the selection of cover art, be it middlemen in the form of a publisher and a separate cover artist or just the passage of time motivating new cover art matching the predominant contemporary aesthetic while the contents of the book remain unchanged. This correlation is the result of at least two factors: 1) that people who have specialized into a very specific niche have shared stylistic preferences that motivated them to specialize into the niche in the first place, resulting in a clustering of aesthetic preferences between niches, and 2) that even when middlemen are involved, those middlemen will try to align the aesthetics of cover art such that they align with the spirit of the book.

If I don't remember wrong, doesn't it put object destructurings on one line if they fit? That's both less readable than putting each destructured member on its own line as well as a cause of unnecessary whitespace changes in your commit history if you ever add one more field to the destructuring that takes it over the line length limit.

A mediocre language is not the same as a language appropriate for developers of mediocre skills. Go could have been made a better language [0] in many ways that would have actually made it easier than it is right now, by avoiding some of the footguns built into the language. This also wouldn't have made the language more prone to being used in a complex manner by metaprogramming enthusiasts and the like. Better often means simpler, not more complex.

[0]: Not that it's not already decent.

You are mistaken if you think that the abundant use of interfaces etc. is motivated by anything along those lines. As someone who often puts basically everything behind an interface and abstracts everything in sight, it's not because it has anything to do with being smart, quite the opposite; it's the only way I can keep any meaningful portion of the code in my head. Give me a 5000-line straightforward / unabstracted implementation of something and I'll struggle, but give me thingDoer.doThing(), behind an interface so I don't even have to know how to construct one, and I'll be happy and able to focus on the task at hand. This applies even if I'm the one who wrote the ThingDoer interface and its implementation(s), and even if it was only 30 minutes ago.