758 karma · joined May 3, 2022
Everything you don't understand looks complicated from the outside.
I don't think that myself, but it's internally consistent.
(This comment is an explanation of a viewpoint, and not an endorsement.)
Remember that it's okay to screw around and break stuff! Sometimes that's the best way to learn. Git's whole thing is tracking history, even the history of the history itself[0]. It is hard to accidentally lose something if you've committed it at least once.
If you're nervous, push your branch somewhere before starting (it's not strictly necessary, but it's the easiest way to get peace of mind).
Nothing you do locally will be pushed back to the remote unless you explicitly do `git push`. If you manage to screw up your branches so badly you don't know how to recover, you can just delete the local branch you broke and re-pull the remote one like it's a new branch, even if it's main/master.
[0] https://ohshitgit.com/, especially the first thing there
Which is fine, if that works for you! Just know that you're using different terms for the same thing (do some work on top of A, then move it to be on top of B instead).
The downsides are the usual sysadmin stuff that comes with running servers for anything. People also get excited about the instance-local timelines on multi-user instances, but again, that's completely missing the point.
Until they grow into the plumbing.
But there are some places where I think Lisp syntax is objectively worse than the alternatives. Parsing Lisp is easy because it shifts all the hard parts to the semantic layer. Everything looks so similar that you have to do a lot more reading, and memorization, to know what's going on at all.
----
To take a supposedly good example from elsewhere in this thread[0]:
> (do-a-thing b (+ a 5))
Is this a function call, or a thing that expands into something totally different? It matters, because (+ a 5) may not be something so trivial - it may have side effects, or be evalulated multiple times. (If it was a lazily evaluated pure function, there would arguably be no point to having macros at all.)
This one is trivially solvable by making macros visually distinct in some way, e.g. Rust's ! suffix, or C's convention of MACROS_YELLING_AT_YOU. It doesn't matter what the syntax is, as long as it exists.
----
Is (progn a b c) a function call or a special form? What about (proxy a b c) or (pregen a b c), both totally plausible names for functions? It's totally possible, even easy, to tell those things apart if you're paying attention, but you have to actually read the word at the start and comprehend it. In languages with dedicated syntax for this, you can just recognize { and } and instantly know what's going on, no need to involve the word-recognition parts of your brain at all. This sounds trivial - nearly all syntax decisions are - but it adds up when everything in the language is like this.
----
[0] https://news.ycombinator.com/item?id=33469932; I've renamed the macro here to make my point clearer, because the obvious response would be "well obviously someMacro is a macro", but in the real world people do not name their macros that.
This may be a lot easier in places where there's an explicit point to discussion beyond the discussion itself - StackOverflow is another non-Reddit example. It's easier to tell people their behavior is unconstructive when it's clearly not contributing to the goal. HN's thing may just be to declare a particular type of conversation to be the goal.
----
Isn't this just the nullability thing again that most people have decided was a bad idea, but with a different name? Instead of two ints, now your function takes two maybe-ints and crashes at runtime if they're not there.
I do think the thing you're asking for (interfaces but for fields instead of functions) is a missing feature in most languages. I understand why it's there, but it's still annoying.
The most well-known language I can think of that explicitly supports it in an otherwise static type system is TypeScript, where a type can be "object containing non-nullable ints called start and end, but with no constraints on other fields that may be there"[0]. It's technically a hash map at runtime, but it's a compile-time type-checked hash map.
In other languages, you could bodge it with a bunch of Java-esque getX/setX functions. I don't blame people for not doing so.
[0] https://www.typescriptlang.org/docs/handbook/2/objects.html
I've found this is actually one of my biggest problems with functional code as currently written: people seem afraid to just declare a struct/record, in lots of cases where it's obviously the right thing.
If everything is a hash, you've just made all arguments optional and now you've invented a bad type system inside your good type system.
If everything is a tuple (more common in my experience, from reading Haskell), now you know (int, int) is actually a pair of ints, but you've thrown away variable names and nominal typing: is it a 2D vector, a pair of indices into an array, or something else entirely?
Defining custom types is the elegant solution to this: you have `struct range { start: int, end: int }` and now your functions can take a range and everything is great.
Imagine trying to get people to accept vaccines, but in a world where we have other types of medicine that usually works well enough for all the things you've developed vaccines for, and the only argument you're allowed to make is "it's obviously better, look at how amazingly liquid the stuff in this syringe is".
You'll still get some resistance, but in our current world, pure FP is much more of a niche thing than pro-vaccine stances. There's a reason for that.
What remains when people advocate for more FP is the more esoteric stuff. (And non-nullability, which inexplicably gets associated with FP surprisingly often despite being orthogonal to everything else in the paradigm.)
I would recommend that, because the connection isn't obvious to me.
In the absolute worst case, wouldn't it be exactly as often (if every fridge immediately fails)?
Though now I'm imagining someone having a fridge fail and then buying two backup fridges just in case.
I'm not sure it works for end-user software; the set of people who can write working tests is usually smaller than the set of end users.
I'm more inclined to think they're very inconsistent, and people are more likely to complain about bad experiences than to loudly proclaim "this company is reasonably okay to deal with!"