1. 6

    Love this. My only complaint is at the end where he stops to go work on “useful” stuff. Programming is an art form and there is value in creative endeavors like this whether or not someone makes money off itl

    1. 2

      I agree that programming is an art form, but “useful” extends to more than just “making money”.

      1. 1

        Reminds me of Cautionary Tales - Fritterin’ Away Genius.

        I’m not saying Bryan Braun is going to be the next Claude Shannon, but there is a strong argument for the benefits of spending non-trivial amounts of time on fun things.

        1. 2

          I’m saying that working on projects like this has value whether or not it helps you in more serious work.

      1. 3

        Opened up my side business, titlereader.com, and I’m learning all the nuances of account creation and whatnot. This week is validating signup emails - useful for account recovery. The solution is to send an activation link in an email, so I had some fun with mailchimp mail merges to get that working.

        1. 13

          i’m waiting for version 3.14, so we can call it pithon

          1. 3

            Well, some would insist it’s halftauthon…

            1. 1

              Ba dum, tsssss

            1. 6

              None of these are particularly convincing to me, as someone who has seen some Perl code and heard more complaints but hasn’t written any.

              1. Perl regexes that I’ve seen are not easily readable. If they are readable when done well but nobody does them well, then the problem is that it’s too easy to use them badly, which is a language design problem. It’s true that regexes in other languages aren’t especially readable either, but as he says, regexes are much more deeply embedded in Perl than other languages, so it’s a bigger problem.

              2. Sigils seem pointless and at the wrong level of abstraction to me. If it’s a simple data structure, I’m not going to forget what it is. If it’s a complex data structure, I don’t want to constantly think of it as a hash or an array, I want to think of it as the data structure it actually represents. Ruby’s use of sigils to differentiate between instance variables, class variables, and so on makes more sense.

              3. I don’t have a problem with the dereferencing syntax mentioned, but it brings up a different question. Let’s say I have an array @array. I want the third element, and I want to store it in a variable. What sigil do I use, if I don’t know the structure of the array? I assume there’s a solution but it seems needlessly complicated that you have to worry about it.

              4. The argument here is “if it bothers you you can use English;” except most of the complaints around perl have to do with readability, and if you’re reading the program of someone who didn’t use English; it doesn’t help that the option is available.

              1. 5

                Sigils seem pointless and at the wrong level of abstraction to me. If it’s a simple data structure, I’m not going to forget what it is. If it’s a complex data structure, I don’t want to constantly think of it as a hash or an array, I want to think of it as the data structure it actually represents. Ruby’s use of sigils to differentiate between instance variables, class variables, and so on makes more sense.

                Perl has roots in linguistic thought [*], so $ and @ are more reasonably thought of as “singular” and “plural” rather than as data types. These provide a ‘context’ to the surrounding code, which perl (uniquely) leverages. Having the context be explicit in the sigils helps understand this process.

                https://www.perlmonks.org/?node_id=738558

                Complex data structures are captured in rich dynamic types with fields and methods, just as in other languages. The same plural and singular context applies to them too.

                [*] e.g. many methods will operate without an argument, and use the default argument $_ which I pronounce “it”.

                my @a = (1,2,3);
                say for @a;
                

                Here “for” is looping over @a. The for loop aliases “it” to each value in turn. “say” is invoked on each iteration without an argument, so it uses “it” and prints it.

                1. 2

                  scalars are singular and arrays are plural, and the sigil is meant to provide context

                  That certainly is a much better explanation for it than the article presented. Why are hashes given a different sigil, though? And why does the other responder say they never use arrays, if they’re one of the two main contexts?

                  1. 6

                    Why are hashes given a different sigil, though?

                    That’s a good question. I find that in perl code, you rarely see a hash sigil except as a declaration. It provides a list context to the RHS of an assignment. Assigning a list to a hash takes alternating elements as key/value.

                    And why does the other responder say they never use arrays, if they’re one of the two main contexts?

                    Plural context is more properly called “list context” (https://perldoc.perl.org/perldata#Context, https://docstore.mik.ua/orelly/perl4/prog/ch02_07.htm, https://docstore.mik.ua/orelly/perl3/lperl/ch03_08.htm) and is more general than arrays.

                    Although, to be honest, I used to use perl arrays all the time. With push/pop and shift/unshift they filled the role of lists, queues, dequeues and vectors in terms of ease of use (I don’t think you can properly fulfill algorithmic complexity of all of those with one data structure, but specialist needs can pull in other modules with those characteristics).

                    e.g.

                    # Seed the work list
                    my @todo = ($path);
                    # Take one item from the front of stuff to do, finish if no more work
                    while (my $next = shift @todo) {
                      #  Get zero or more work items from doing this one
                      my @additional_work = process_file($next);
                      # append new work items
                      push @todo, @additional_work;
                    }
                    

                    Perl’s philosophy is at odds with programming language development since the early 2000s. “There is more than one way to do it” (TIMTOWDI) also “Easy things should be easy, hard things should be possible” a.k.a. Huffman coding for the programmer. Python had broadly the opposite approach (“one way to do things”, “explicit is better than implicit”) and was more in tune with the way the world went.

                    This leads to a language with a very different approach and culture to others. One can rightly criticise perl for having many ways of doing things (just as one can rightly criticise C++ for the same reason). That is a strength and a weakness, mostly a weakness at scale, where a new dev might have to understand a large amount of the language to read code. But to a newbie, learning to write code, it can be an advantage.

                    List context and abbreviation also enabled powerful map/filter tools some time before python got list comprehensions, and when such things were more a province of the lispers. (perl also had proper closures before many other languages - https://www.amazon.co.uk/Higher-Order-Perl-Mark-Dominus/dp/1558607013). Perl hashes (a.k.a. dicts, maps) “auto-vivify” so you don’t need to handle the uninitialised case. Some code to calculate distribution of word lengths:

                    #!/usr/bin/perl
                    use Modern::Perl;
                    
                    my @words = qw/the quick brown fox should be nicer to the lazy dog/;
                    my %lengths;
                    $lengths{$_}++ for map length, @words; # length takes $_ as arg, provided by map
                    foreach my $k (sort keys %lengths) { # list context operators (keys, sort - compose naturally)
                        say "$k => $lengths{$k}";
                    }
                    

                    One might think that one can control the breadth of the language in large projects with coding standards, but both perl and C++ arguably fail here.

                    But perl was (and is):

                    • concise
                    • expressive
                    • fast (compared to other languages in its niche - i.e. python + ruby. JS got a big speed bump later)
                    • well designed (although one may disagree with the design goals)
                    • fun
                2. 1

                  Re point 3

                  my @array = (1,2,3,4);
                  my $third = $array[2]; # 0-based indexing, referencing a single entry returns a $calar
                  my @first_last = @array[0,3]; # slice returns an @rray 
                  

                  To be honest, outside of introductory programming and (in my case) coding contests, arrays are rarely used other than indirectly. The true workhorse of Perl is the hash(reference).

                  1. 1

                    It’d work for a hash as well, but my question is a bit different: If you have a data structure you fill at runtime, so sometimes it could be (1,{'a' => 'b'},2,3) but other times it could be (1,2,{'a' => 'b'},3). Or any other access pattern where you have a heterogeneous array, or a hash where the values are of different types. And you access $array[2]. Since you don’t know if it’s a scalar or a hash, what sigil do you use for the variable?

                    1. 1

                      Well to be pedantic, {'a' => 'b'} is a reference to a hash, which is a scalar, so it would be $third in either case. Of course it would sometimes be an integer, and sometimes it would be a hash reference, but that’s a separate (more serious) issue ;)

                1. 6

                  A long time ago I found a site for the UUID Preservation Society, encouraging all programmers to limit their use of UUIDs lest they run out. This seems like something they would get behind.

                  1. 1

                    It sounds like, for the author, the desktop metaphor isn’t useful because he isn’t using his computer to do work, he’s using it as an information retrieval and storage system. I think what he wants is a catalog, and probably to talk to a librarian about how they organize libraries.

                    1. 8

                      I am desperate for someone to tell me what makes a font “cute”. This isn’t a joke, I’m not making fun, I’m being entirely sincere. I have been baffled by emotional reactions to fonts for a long time and I really want to understand.

                      1. 17

                        I’m not a typesetting expert by any means, but it’s the feeling evoked by looking at the font.

                        If a font has sharp, jagged edges, it looks industrial like some sort of equipment.

                        A font with thin strokes and wide serifs has a formal feeling to it like old style text in an old newspaper.

                        A cute font is generally going to be rounded, without serif, and with uniform thickness across each stroke with some optional flourishing. These fonts remind the viewer of a small cute animal with it’s rounded edges.

                        Aside from the lettering looking like some sort of small animal, the lettering is a much cleaner version of the type you would expect a child to make while writing with a marker, crayon, simple paintbrush or some other wide tipped implement and may remind the viewer of their childhood.

                        These are the things I see. Maybe someone else can offer some more enlightenment.

                        1. 2

                          I don’t know that I would have put anything this way, before reading your comment, and “cuteness” isn’t really a thing I generally experience from fonts (unless we’re talking about emoji), but I want to voice my support for letters that look like some sort of small animal.

                        2. 8
                          1. Babies are the baseline standard for adorability. They have big round eyes. They have big heads relative to their bodies. Adorable babies are also a bit chubby with gentle curves that have a large radius of curvature.
                          2. To make a font more adorable, you need visual cues which trigger the adorability reflex. If you don’t have that reflex, I can still describe some analytical measures that work (okay, they work for me). Give it a large x-height relative to the caps height. That triggers the “babies have big heads” pattern. The lower case letters should contain big round circles, when there is a choice of letter form, which evokes the “babies have big round eyes” pattern. For the lower case ‘a’, you want the “single story” version, often associated with italic and san-serif fonts (but not exclusively), as opposed to the “two-story” version. Similarly for lower case “g”, you want the simpler version that has the bigger “head”. Also, san-serif is more adorable than serif.
                          1. 14

                            Babies are the baseline standard for adorability. They have big round eyes. They have big heads relative to their bodies. Adorable babies are also a bit chubby with gentle curves that have a large radius of curvature.

                            Not gonna lie, I had to giggle because this is the first time I’ve seen someone attempt to come up with an objective measure of cuteness. How about we give it a unit of measure? I propose it be named an uwu. “The font was at least 30 centiuwus.”

                            1. 1

                              Hmm, I know babies are the cuteness standard but I never thought about that removed from the features themselves. Thanks!

                            2. 2

                              A few things make me think Fantasque Sans Mono is cute. It’s got relatively chunky strokes (the “m” has lots of “ink” to it). I put a sample up at https://m.bonzoesc.net/@bonzoesc/106105499791357051

                              There’s enough ornamentation to not be austerely geometric like Futura or Avenir, but there’s an imprecision and lack of uniformity to it. The top of the “k” is looped, and the loop is more tadpole-shaped than it is round, just like the void in the bottom of the two-story “a”. The middle of the “e” is gradually sloped, although that’s really subtle. The “g” is a fun two-story thing, with a bigger void in the basement than the ground floor. The leg on the “R” is at a funky angle. The “3” is bottom-heavy.

                              I guess to an extent it might be a feeling of irregularity, lots of things that are purposefully at conflicting angles, terminals on strokes being a bit suggestive of the hypothetical pen starting at an angle before aligning with the grid. Stuff that really only works on retina screens and in a way that an OS vendor wouldn’t pay for.

                              1. 1

                                Thanks! That makes a lot of sense. I guess I’ve never really carefully looked at individual glyphs.

                              2. 2

                                for me it’s how it’s rounded with a wide radius. kind of like those fridge magnets kids play with.

                              1. 1

                                I had fun reading that.

                                1. 4

                                  I think the unmentioned part is that he had a boss willing to allow him three uninterrupted weeks to do this.

                                  1. 14

                                    Oracle, the world’s largest patent troll, would really like you to use this license that unambiguously lets them use your stuff.

                                    1. 1

                                      Few questions from someone not close to either the erlang world or the js world:

                                      1. The author uses Node and Javascript pretty interchangeably. Are all JS frameworks basically the same as far as concurrency goes?
                                      2. If my main erlang process needs to wait for the ps process to finish, what’s the advantage of having a separate process? isn’t the whole point of multithreading so that your main thread can do stuff while waiting for the DB to finish?
                                      1. 1

                                        Are all JS frameworks basically the same as far as concurrency goes?

                                        Pretty much. The only way to have concurrency in JavaScript is to use async. JS by definition is single-threaded with async capabilities.

                                        If my main erlang process needs to wait for the ps process to finish, what’s the advantage of having a separate process?

                                        Error handling mostly. In Erlang failure of one process do not propagate to other (unless explicitly requested).

                                        isn’t the whole point of multithreading so that your main thread can do stuff while waiting for the DB to finish?

                                        Erlang processes weren’t created for “multithreading” but for error isolation. The parallelisation was added later. But returning to your question about “doing other stuff while waiting for DB to finish” - you still can do so, just spawn other process (it is super cheap, as Erlang processes are different from OS processes). You still will need some synchronisation at some point, in Erlang each process is synchronous and linear, and it relies on messages to do communication between processes. So “behind the scenes” in function Postgrex.query/2 (in Erlang functions are defined by module name, function name, and arity, you can have multiple functions named identically with different arity) is something like (quasi-simplified as there is more behind scenes, for example connection pool, and most of the code there will be hidden behind functions, but in it’s “core” it reduces to that):

                                        def query(pid, query) do
                                          ref = generate_unique_reference()
                                        
                                          # Send message to process identified by `pid` with request to execute `query`
                                          # `ref` is used to differentiate between different queries. `self/0` returns PID of
                                          # current process, we need to send it, so the Postgrex process know whom
                                          # send response to.
                                          send(pid, {:execute, self(), ref, query})
                                        
                                          # Wait for response form the Postgrex process
                                          receive do
                                            {:success, ^ref, result} -> {:ok, result}
                                            {:failure, ^ref, reason} -> {:error, reason}
                                          after
                                            5000 -> throw TimeoutError # if we do not get response in 5s, then throw error
                                          end
                                        end
                                        

                                        So in theory, you could do different things waiting for the response from the DB, this is for example how sockets are implemented in gen_tcp and gen_udp, these just send messages to the owning process, and owning process can do different things “in the meantime”. In this case, just for convenience of developer, all that message passing back and forth, is hidden behind utility function. However in theory you can do so in fully asynchronous way. This is almost literally how gen_server (short for generic server) works.

                                        1. 1

                                          So you’d spawn a new process that makes a DB call, then to handle the result that new process spawns other processes? And in the meantime the original process can keep going? What if you need the result in the original process? Or do you just not design your code that way?

                                          1. 1

                                            So you’d spawn a new process that makes a DB call, then to handle the result that new process spawns other processes?

                                            No and no. You are always working within scope of some Erlang process, and Postgrex start pool of connections when you do Postgrex.start_link/1. So you do not need to create any process during request, all processes are already there when you do request.

                                            And in the meantime the original process can keep going?

                                            You can do so, however there is rarely any other work to do by that given process. When writing network application with TCP connections, then in most cases each connection is handled by different Erlang process (in case of Cowboy each request is different Erlang process). So in most cases you just block current process, as there is no meaningful work for it anyway. However that do not interfere other processes, as from programmer viewpoint Erlang uses preemptive scheduler (internally it is cooperative scheduler).

                                            What if you need the result in the original process?

                                            My example above provides information in original process, it doesn’t spawn any new processes. It is all handled by message passing.

                                        2. 1
                                          1. Yep, Node is just a wrapper around V8 (the JavaScript runtime from Chromium) plus an API that provides access to system resources (filesystem, networking, OpenSSL crypto, etc). The concurrency being discussed is common across all JavaScript runtimes, and isn’t specific to any of the API that Node provides. I don’t know why the author says “Node” in the article, they’re talking about JavaScript.
                                          2. No idea, I haven’t fully escaped JavaScript yet.
                                        1. 56

                                          I work on Kubernetes on the daily, at work, and I still think this is ridiculous. Now, not only do you have to setup a Kubernetes cluster, you have to deal with maintaining Kubernetes itself. And here’s the kicker, all this for a static blog? Just use GitHub Pages or Netlify.

                                          “Just do X, that’s so much simpler”

                                          Yes! Any other deployment solution is most definitely simpler than Kubernetes — at least, for the use-case mentioned here.

                                          I think Kubernetes has fallen into the Vim/Haskell trap. People will try it for 10 minutes to an hour then get fed up.

                                          Well, yes, but I don’t fault them for giving up on it while attempting to setup their blog, heh.

                                          I was told Vim was too complex and I should just use Sublime Text or Eclipse, but I bit the bullet and now I fly through code, and feel like I’m moving through molasses when I have to use, e.g. XCode. Since that experience, I give complex tools a bit more of a chance. Sometimes I’m burned (why did I ever switch to Dvorak, total waste), but sometimes I find a Kubernetes.

                                          Vim is not a good analogy here. Vim is merely complex in its learning curve — it is still a relatively simple text editor; low resource consumption and blazing fast. Kubernetes is akin to spinning up a full-fledged browser environment to edit your code. Oh wait.

                                          1. 18

                                            Obviously OP needs to be teaching classes on Kubernetes. If they grokked the entire Kubernetes documentation “in a few hours”, then clearly they are a lot smarter than me… It took me over a year to be completely comfortable in the k8s API.

                                            1. 5

                                              Now, not only do you have to setup a Kubernetes cluster, you have to deal with maintaining Kubernetes itself.

                                              Agreed that this is like swatting a fly with an aircraft carrier, but judging from their mention of DigitalOcean provisioning nodes, I’d assume they’re using the hosted Kubernetes product, so all the hard work of maintaining it isn’t really their problem.

                                              1. 4

                                                It’s essentially just a calculation:

                                                benefit = time_spent_on_k8s - time_saved_by_k8s
                                                

                                                In some cases the benefit might be positive, but in a lot of cases I suspect it might be negative. It’s a classic case of spending a day to automate a 10 minute task you need to do only once every few months.

                                                1. 1

                                                  I fundamentally disagree with a calculation like this. It’s not about time and it never will be. It’s about cognitive burden, if I need to remember a task every few months — no matter how small it is — then I now have a source of stress. What if I forget it? What if I am travelling somewhere or at a wedding or whatever? If the backlash is small (a little money or some slight increase to maintenance burden) then I can just forget it and it’s fine but if it’s important to me then I will absolutely want to automate it.

                                                  The equation should be about the cognitive burden of a task, how much cognitive capacity does it passively occupy? how much cognitive capacity is actively used when performing the task? How much stress can I save by being allowed to forget about it?

                                                  My cognition is my most finite resource and I’d like to free up any leaks as far as possible. That is why I will never use kubernetes for my blog and it’s also why I prefer to have a declerative operating system.

                                                  1. 1

                                                    We’re talking about doing a manual deploy vs. an automatic deploy; this isn’t something that you can forget or need to stress about; it’s something you either do or don’t. We’re not talking about SSL certificates that can expire or some such.

                                                    1. 1

                                                      Oh I thought you were trying to suggest that this time calculation holds in general and got triggered. My bad.

                                                      1. 2

                                                        Yeah, that’s alright :-) In reality, of course, things are always more complex than the simple reductionist faux-calculation I made before. For example if it’s easy to accidentally do something wrong then it might be a good idea to script it no matter what. This is why I tend to script database stuff instead of running SQL queries directly even when I know I’m only going to need it once or twice. We’ve all forgotten a where clause or accidentally put a ; before a where or something like that, and depending on the query this can be a big oopsie.

                                                        Anyway, I think the most important thing to keep in mind is that these sort of things are essentially a calculation. Sometimes it’s easy to get carried away with scripting stuff (or things like refactors) but when you stop and think, you realize you’re actually not really working on something that’s all that useful.

                                                        A while ago I spent half a day on a script to copy Stripe products from live mode to testing. It still didn’t work after half this day, and then I realized I was automating a 10-minute boring manual task I need to do once a year maybe. It was a waste of time in hindsight.

                                                  2. 1

                                                    Swap those operands there if you want a high benefit to be a good thing.

                                                    1. 1

                                                      Oops 😅 Too late to edit now. Ah well, I think the point should be clear.

                                                  3. 2

                                                    Yeah it’s more akin to booting up a whole virtual LispMachine just to edit text.

                                                    (Just a joke emacs users, I’d take elisp over yaml+helm any day)

                                                    1. 2

                                                      Don’t be ridiculous, we emacs users don’t spin up a whole virtual LispMachine just to edit text, we use nano for that. We spin up a whole virtual LispMachine so we can convince our bosses we’re editing text when we’re actually playing tetris.

                                                    2. 2

                                                      Oh my gods, I would vote it up to the stratosphere if I could.

                                                    1. 18

                                                      carcinization is getting out of hand.

                                                      1. 2

                                                        I would have also liked to hear why the author thinks OOP should be revived. The list at the beginning and most of the article is focused at fixing the warts, but I wonder what the author thinks the advantages are.

                                                        1. 1

                                                          This really really needs some CSS to be readable.

                                                          It seems to me that users on an outdated version of the library, even if they are only hurting themselves, are still unhappy users, and nobody wants unhappy users. Plus you still have to support them.

                                                          1. 2

                                                            So, then, how does one handler errors internal to a program, then, if a given error indicates that a program should abort? Or that a core assumption of the program has been violated?

                                                            Or the sorts of conditions that indicate those sorts of problems built to be impossible to achieve, by ensuring that all possible parsing/data-ingest errors happen at the edge of the program?

                                                            1. 4

                                                              So, then, how does one handler errors internal to a program

                                                              Yeah, in a functional program written with the “functional core, imperative shell” model, there’s no such thing as errors “inside” the codebase, other than bugs. Everything that could fail for reasons that could be predicted ahead of time is handled by the imperative shell at the outside.

                                                              1. 1

                                                                How would you handle a divide-by-zero error in a calculator?

                                                                1. 5

                                                                  It is not an error, it is a correct state that has to be encoded somehow. Dividing by zero is a valid operation, not a bug.

                                                                  1. 4

                                                                    As @Leonidas said, dividing by zero is an expected operation for a calculator app, so it isn’t considered an error by the definition used here.

                                                                    That can feel a little disingenuous because you can kind of go “reductio ad absurdum” on it and say that nothing in your program is an error: you should always expect the network might be down, the file disappeared because the hard drive caught fire, you ran out of memory, etc.

                                                                    I don’t have a good response to that because it’s probably correct.

                                                                    I feel like there are three different things that we collectively call “errors” for shorthand, but that might be doing some harm:

                                                                    1. Legitimate programmer mistakes. Indexing an array beyond its bounds, etc.
                                                                    2. Invalid state caused by inputs. This is the user typing in “100/0” into the calculator. This isn’t an “error”- the programmer is completely able to predict that this will happen (and WHEN it may happen!).
                                                                    3. Invalid state caused by the universe. OOM, network died mid-request, bad file permissions, memory corruption from cosmic rays, etc.

                                                                    I believe all three should be handled differently in the vast majority of programs.

                                                                    IMO, case 0 should just cause a crash.

                                                                    Case 1 should be handled by your type system if you’re using one. When doing an arithmetic operation, be prepared to return NaN or a DivByZero variant of an ADT or something.

                                                                    Case 2 should “throw an exception” (or equivalent) to be handled at the top loop of the program. Show a message to the user about what went wrong, try to clean up if possible, and then shutdown or try again or whatever.

                                                              1. 2

                                                                I like articles like this because they remind me exactly how wide and deep the world of programming is. There is a whole subculture of C++ programmers that apparently blindly prefer emplace_back to push_back. I read the article, and I still don’t understand why, and I’ve never heard of it before. It’s nice to know that there’s still lots more to learn.

                                                                1. 1

                                                                  Emplace calls le you construct the object directly in the collection, instead of constructing it on the stack and then moving it into the collection. It’s a small optimization but significant when you’re writing performance sensitive code. (And some objects can’t be moved efficiently, making it more important.)

                                                                  1. 1

                                                                    I’m not sure what you mean by collection. Do you mean directly onto the heap?

                                                                1. 2

                                                                  Yeah, why would you ever expect a package manager to expose a package you were supposed to use?

                                                                  1. 2

                                                                    The problem here is that they’re using a package manager to install a language and then using a hacky tool (virtualenv) which does some symlinking of binaries which are dynamically linked to libraries provided by that package manager, and then install and link other libraries using an entirely different package manager of which the original package manager is completely oblivious (pip inside the virtualenv), and then upgrade the underlying package against which the extension libraries were linked.

                                                                    Of course this is going to break! In the extreme, think of this like installing Python 2, installing some libraries with pip and then upgrading to Python 3, and expecting those pip packages to just work without first rebuilding them against the new Python.

                                                                    The real solution is to either do everything through the package manager (which means you’re stuck with whatever versions of Python libraries are provided by homebrew) or nothing (which means installing Python with some other tool).

                                                                    edit: After re-reading, it seems to me Homebrew supports having multiple Pythons installed at the same time. So I guess the real problem is that it doesn’t have a mechanism to remember which Pythons you manually installed and want to keep. Or maybe it does and people are just relying on Python being installed through whatever other package and are not explicitly installing a specific version they want to rely on.

                                                                    1. 3

                                                                      I think you’re missing an important part of my article. The workflow you described worked very well for years, right up until Homebrew added two changes that caused everything to break, most notably the automatic cleanup that causes previous package versions to be deleted automatically.

                                                                      To me, the real problem is that Homebrew does not mention this on their Homebrew and Python page. They could say, “Don’t rely on Homebrew Python versions, which can disappear at any time. You might be better off using asdf, pyenv, or Pythonz tooling to install and manage Python interpreter versions.”

                                                                      1. 2

                                                                        To me, the real problem is that Homebrew does not mention this on their Homebrew and Python page.

                                                                        This would be a very helpful addition to the page. Have they refused the patch, or just not applied it yet?

                                                                        1. 1

                                                                          Fair enough; they broke something which used to work. Maybe it worked “by accident” in their vision, but if so many people relied on this accidental behaviour, it would behoove them to clearly inform users about it.

                                                                          Note that I was more responding to the parent comment than your post though.

                                                                        2. 3

                                                                          Think of this like installing Python 2, installing some libraries with pip then upgrading to Python 3.

                                                                          The problem here is that Homebrew decided to upgrade, not the user. If I upgraded to Python 3, I wouldn’t expect anything to work, but I didn’t - Homebrew did it for me without asking.

                                                                          But what I’m really complaining about is the way the article describes it as “a misunderstanding” like users should know better. It’s a package manager. Exposing packages under obvious names that the user is not supposed to install is a horrible design failure, and I don’t like the way the article blames the user.

                                                                          1. 4

                                                                            I don’t like the way the article blames the user.

                                                                            Article author here. Thanks for the constructive feedback. My initial reaction to this was, “Huh?? Did we read the same article?” Then I re-read it with your perspective in mind. And now I see your point. Allow me to explain how we got here. Short version: I was trying to be kind.

                                                                            I actually wrote this article last summer, soon after Homebrew’s changes started wreaking havoc. Around that time, a user of VirtualFish (a virtual environment manager for Fish shell that I maintain) encountered the same infuriating problem and posted an issue about it in the VirtualFish repository tracker. I responded by posting a long rant and venting my frustration with how poorly the transition to the new Homebrew behavior was managed. In that comment you will see the basis upon which my article was written, which I purposely softened so as to respect the hard work that unpaid, volunteer Homebrew maintainers put into the project, for free.

                                                                            I fear that in the process of toning down my frustration, I inadvertently left out the part about Homebrew’s culpability in neglecting to inform users about its suitability for Python development, leaving the opportunity for folks to walk away with the impression that I think the fault lies with users and their inability to understand. That was absolutely not my intention — quite the opposite. Mea culpa.

                                                                            I hope that after reading my above-linked screed, you will see that I do not hold users accountable here. Perhaps I should amend the article to make that clearer. Thanks again for communicating your perspective, which will help me express myself better in the future.

                                                                            1. 1

                                                                              Well that makes sense. Thanks for the explanation!

                                                                          2. 1

                                                                            There is still one way that it can break things even if you used another tool like pyenv to install the Python interpreters you actually use: pyenv compiles the interpreter locally, linking against the set of shared libraries you have at the time. Which doesn’t matter much for Python, except in the case of OpenSSL. Sometimes, Homebrew will “helpfully” clean up an older OpenSSL that some pyenv-compiled interpreters linked against, and suddenly those interpreters fail.

                                                                            1. 1

                                                                              Can’t you tell pyenv to link Python against the system-provided OpenSSL though? Or is that so hopelessly out of date that it’s unusable?

                                                                              1. 1

                                                                                I don’t know how it is now. I know at one point you didn’t really have a choice because the system OpenSSL was too old for things like pip to even be able to speak to the Python Package Index.

                                                                        1. 12

                                                                          SQL maps very closely to the underlying set theory, which makes it very easy to reason about.

                                                                          1. 5

                                                                            I do not think it maps closely to set theory at all. There’s the Null value, rows can be duplicated, etc. People have been criticising SQL since the 80s because it’s not close to set theory.

                                                                            1. 2

                                                                              There are variations on set theory that account for duplicates.

                                                                          1. 3

                                                                            This is bringing the Gell-Mann Amnesia Effect to mind. The author gives a bunch more examples of articles that get just everything wrong.