1. 39

    An interesting parallel, which works for the author, but doesn’t hold universally. Go and Ruby are fundamentally different.

    Unlike Rust where the compiler constantly shouts “fuck you” even though you are trying to do your best to serve their majesty and the rules they dictate, Ruby never gets in your way.

    This sentence is bad in many ways. First of all: it is toxic. Maybe error messages weren’t that useful when the author tried Rust as they are nowadays, but I doubt that. Failure to understand WHY things fail should be a priority to any developer. And having things actually fail loudly as early as possible should be considered a huge benefit. And then saying “Ruby never gets in your way” is plainly wrong. I would let “never gets in my way” slide, of course.

    1. 23

      “Ruby never gets in your way”

      Ruby gets in the way, but later on. Whether it’s the rare exception that must be tracked down or the large refactor that demands a whole-system understanding of the system, Ruby is not any less in the way than Rust or Java; it gets in the way of getting things done, but in a different manner and at different times.

      1. 8

        Ruby gets in the way, but later on.

        Very much this. I’m sure it’s possible in other languages but I’ve never seen people deliberately create ungreppable methods in any other idiom, such as

        %i(foo bar baz).each do |sym|
          define_method("prefix_#{sym}_suffix") do
            # method body
        
        1. 7

          C++ preprocessor/template dark magic can get horrible quickly. It’s not just that people actively subvert auto-complete or grep. It’s also that these people never document these types of things, and they often do it when there’s an easier, much less bad way to accomplish what they’re trying to do.

          1. 3

            I generally make strong recommendations to the teams I’m on to keep things greppable, so this is more about best practices than the language itself being significantly flawed. (I understand the argument that foot guns shouldn’t be there to begin with.) define_method can be useful if there is a large number of things you want to iterate over and make methods for. Having said that, I basically never use define_method, myself.

            1. 4

              Common Lisp - but the debugger system will let you look the method up, and likely the method arises from a macro call-site which you can find from the method invocation etc.

              1. 2

                I would say the difference is that Common Lisp developers typically try to avoid doing crazy macro stuff if they can avoid it. I suspect most Ruby devs are the same, but there seems to be a vocal minority who love using metaprogramming as much as possible.

                1. 4

                  There’s a similar minority in the CL community. Metaprogramming pushes happy buttons for a bunch of people.

                  That doesn’t make it any more supportable, mind you.

                  1. 2

                    Back in the day it seemed mostly relegated to high end dev shops. I heard multiple stories about these folks creating metaprogramming monstrosities. Sure it worked, was BDD-tested, etc, but unless you were already familiar with it, the code was pretty hard to touch.

                    1. 2

                      I’m not sure I buy the implicit argument here that all code must be maximally accessible.

                      Metaprogramming is just build-time code execution that is able to impact code at runtime. It is a skill you can learn like any other.

                      1. 3

                        Sure, but it requires additional thought to read, and makes things much less discoverable by tools (such as grep/ack/ag/etc, as mentioned earlier).

                        Accessibility is a virtue when the code is going to be read by people other than the original author.

                        1. 2

                          I’m not a big fan of it in general, but metaprogramming done well makes code easier to read and understand.

                          For example, autowrap is used to create Common Lisp bindings to C and C++ libraries. All of the work is done with a “c-include” macro, which does exactly what it sounds like, and is much easier to read and understand than bindings written by hand. There’s a real life example in the GDAL bindings I’m making. A single macro call generates a few thousand lines of CFFI glue code and definitions.

                          Poor discoverability might depend on the implementation. Bindings from autowrap are tab completable in the REPL and everything.

                2. 2

                  Dynamic programming? It’s possible in python too. Useful for generating unit tests methods.

                  1. 2

                    Dynamic programming is something quite different. You’re looking for the term “metaprogramming”.

                    1. 1

                      thanks for catching

                  2. 2

                    Oh it’s perfectly doable with PHP’s __call :) https://www.php.net/manual/en/language.oop5.overloading.php#object.call

                    1. 2

                      I’ve switched opinions on this so many times I’ve lost count.

                      However, I remain convinced that:

                      • metaprogramming is an extremely sharp tool, which can end up hurting you later
                      • sharp tools aren’t inherently bad

                      Programming will always have ways to shoot yourself in the foot. The best we can do is make those ways explicit, contained, and able to be reasoned about in a sane way.

                      1. 2

                        I think we can do better. We can eliminate footguns where it makes sense. I have some examples, not all will agree they are footguns. Python eliminated switch statement. Go made it so the default is to not fall through. also Go doesnt have a ?: syntax. Further, Go only allows booleans with conditionals. So you cant do something like:

                        n1 := 10
                        if n1 {
                           println("something")
                        }
                        

                        At first this was annoying, but it make sense. By only allowing this:

                        n1 := 10
                        if n1 != 0 {
                           println("something")
                        }
                        

                        You dont have to think about or answer the question “what fails the condition”? Ruby only fails with nil and false. JavaScript fails with anything falsy. You just avoid that problem alltogether at the cost of a little terseness.

                  3. 9

                    I also find it weird because as a n00b Rust developer, I find the compiler absolutely lovely. It feels like my pal, cheering me on and giving helpful suggestions. (I come from a Java and Javascript world, where compiler messages can be pretty… terse.)

                    Yeah, Rust can be difficult to write, but the compiler’s error messages sure make it pleasant.

                    1. 8

                      “This A -> B looks incorrect, did you mean A -> C?” changes stuff “This A -> C looks incorrect, did you mean A -> B?”

                      Not debating the point you’re trying to make but the author sounds like many people who were only starting with Rust. I can 100% feel the sentiment that’s made in the article, and I’m a huge Rust fan and would love to use it more - but right now I’m at a stage where giving up in frustration sometimes happens. And this has nothing to do when it was. You call it toxic, I can wholeheartedly agree with “been there, seen that”.

                      1. 3

                        This “try {A, B, C} forever” type of errors happens very often with Rust noobs (to clarify: that’s a learning curve, not a dig at someone being a novice) who try to do something that is impossible to do, or impossible to prove to the borrow checker. Unfortunately, the compiler is incapable of understanding that it’s not a problem with a particular line of code, but the whole approach. For example, use of references as if they were pointers digs a deep hole the compiler won’t get you out of (it’ll try to annotate lifetimes instead of telling you to stop using references and use owned types), e.g. don’t write a linked list, use VecDeque.

                        The “toxic” bit was about rather harsh framing of the issue. Although, I don’t blame anyone for not liking Rust’s strictness. It has its uses, just like Go/Ruby more lax approaches.

                        1. 2

                          Congratulations on drafting an even more smug answer to the thread.

                          I think you’re missing the point and you also don’t need to explain the exact issue at hand, I was just citing an example of a clearly not impossible thing (the one where I encountered this the last time was simply about getting some form of text into a function, and of course it was a String vs slice problem but it wasn’t obvious at the time).

                          And yes, I do think that I prefer a screen full of old Clojure’s unhelpful stack trace than rustc telling me something completely wrong while trying to be helpful. At least then I’m not led on the wrong path because I usually trust my compiler.

                          1. 4

                            I don’t see how @kornel was being smug here. He’s saying that if a beginner tries something that the borrow checker considers impossible, it will cycle between several incorrect errors instead of raise the actual issue. Is it the “noobs”?

                        2. 3

                          but right now I’m at a stage where giving up in frustration sometimes happens

                          It’s interesting that so many people are running into this. I learned the rules of the borrows checker, knew moves from C++ already, and that was pretty much it. Sure, I sometimes run into problems with the borrows checker (less than when I started writing Rust), but multiple immutable xor single mutable is easy to appease. Lifetime issues can be more difficult, but typically reasoning about the lifetimes solves them pretty quickly.

                          I wonder if it has to do with different problem domains that people are trying to tackle (I mostly work on machine learning and natural language processing, have not done much web backend programming with Rust), or the languages that they come from – I used C++ for many years prior, so perhaps I had to reason about ownership anyway (compared to e.g. someone who comes from GC’ed languages)?

                          I am not saying that Rust’s learning curve is not steep or that I am some genius that understands Rust better (most definitely not). But more that it would be interesting to investigate which problem domains or programming language backgrounds make it easier/harder to pick up Rust.

                          1. 5

                            I think the experience of Rust coming from C++ is vastly different than coming from Ruby or Python. Those latter languages shield the programmer from a number of things that need to be thought about with a systems-level language. Confronting those things for the first time is fraught.

                            1. 2

                              Users of GC’ed languages sometimes have to reason about ownership and lifetimes too. However, they are not punished anywhere as badly as C++ users for failing to do it correctly. They just trap the error, log it, do their best to recover from it, and move on.

                              It seems disheartening to explicitly write code whose sole purpose is to recover from errors that you will inevitably make, though.

                              1. 2

                                Indeed, it’s the unforgiving strictness of Rust ownership that gets people by surprise, even if they know it at a conceptual level.

                                The second problem is that with GC design patterns can reference any data from anywhere, e.g. it’s natural to keep mutual parent-child relationships between objects. The borrow checker wants data to be structured as a tree (or DAG), and it comes as a shock that more complex relationships need to be managed explicitly (with refcounting, arenas, etc.)

                              2. 2

                                No idea. But I only started doing C++ after Rust and I haven’t had any of these problems with move semantics. But I wouldn’t claim I’d never created a bug because of it :P Rust was in learning in my spare time, for C++ I had a whole team to review my code and help me out.

                                Also I’m not saying I ran into this constantly - just that it happened several times. And I think my code tends to attract these kinds of problem, if it’s not “5% preparing data into proper data model, then 95% working with it” but being exactly on the surface.. like when i wrote my IRC bot - it’s 90% string handling and a little bit of networking.

                            2. 4

                              I’ve seen somebody saying something among the lines of “C is harder than Go because Go’s compiler will scream at me if I have an unused import and C’s doesn’t”. I can’t say I understand this mentality.

                              1. -2

                                Unlike Rust where the compiler constantly shouts “fuck you” even though you are trying to do your best to serve their majesty and the rules they dictate, Ruby never gets in your way.

                                I wonder if comments like these come from the kind of people who were throwing a hissy fit as a kid when their teacher told them to do their homework.

                                1. 16

                                  While the original article is needlessly hostile, so is this response. For good or for bad, programming is a gratification-driven activity. It is not hard to see why more people find it gratifying to write a program that runs, rather than a program that type checks.

                                  Besides, on a purely intuitive level, type errors are not necessarily the easiest way to understand why a program is flawed. Errors, just like anything else, are best understood with examples. Watching your program fail on a concrete input provides that example.

                                  (Admittedly, fishing for counterexamples in a huge state space is not an approach that scales very well, but what use is scaling if your target audience does not want to try the alternative you suggest even in the tiniest cases?)

                                  To illustrate the power of modeling concurrency with types, one has to give concrete examples of idiomatic programs written in non-typeful concurrent languages that contain subtle bugs that would have been caught by Rust’s type checker.

                              1. 6

                                I am certainly not part of the Haskell community, but I wonder this: Will the name “Ormolu” (which seems to mean a gold in color alloy) hinder widespread adoption of it? When I think about gofmt, Go’s formatter, even if it weren’t part of the standard toolchain, I could find it really easy with a search like “go format”, and remember it’s name, trivially. How does a newcomer to Haskell learn about ormolu, and remember that it’s there, as opposed to discovering it because of the name ghcfmt or haskfmt or some other variation like that?

                                1. 2

                                  If you’ve never used a code formatter before I think you’d probably find one first in the options of your editor plugin or part of a linting system at work. Once you experience one and decide you like them, one of the first things you search for when learning a new language is a code formatter, and Ormolu already shows up in the results. There are already others named haskell-formatter and hformat as well, using a name related to this might make it even more confusing as to which one is best. As for existing users, I usually learn about it from Twitter or a coworker, and to me a distinctive name is easier to remember.

                                  1. 2

                                    I doubt the name would harm adoption. One of the more awkward names that comes to mind is zxcvbn, and I don’t think its adoption was harmed by it either.

                                    1. 2

                                      It certainly ought not to be called ghcfmt nor haskfmt because it is in no way an official formatter!

                                    1. 2

                                      Why would someone use a language in 2014 with such a restrictive license? Is it magic or something?

                                      1. 3

                                        That’s of the point of the submission. The wacky license has been a barrier to adoption (see decent HN discussion here), and the author is finally considering FOSS.

                                        1. 1

                                          Right; as far as I can tell, no one is using it with the current license, despite it having a fairly interesting type system.

                                      1. 1

                                        I wonder why they chose to fully fork it instead of contributing back to nginx? Is the project difficult to contribute to for some reason? I’d love to be able to add modules dynamically without totally recompiling.

                                        1. 1

                                          It’s cool to see this is possible now, but can anyone think of a better API than dropping down to arel_table? I’d rather write the SQL by hand honestly.

                                          1. 1

                                            Isn’t this pretty much just Erlang but with a clusterfuck of languages?

                                            1. 1

                                              The author says “Message based systems have been around forever. The way I’m applying the concept is what’s new and interesting (at least to me).” But I’m also lost on exactly what the new ideas are.

                                              It sounds like the author is wishing for a “Staged Event Driven Architecture” http://www.eecs.harvard.edu/~mdw/proj/seda/ — which was a really cool project. I think the SEDA idea has a lot of potential, but unfortunately the developer Matt Welsh, as an academic, was not incentivized to continue development. Matt discuss this problem, in general, here http://matt-welsh.blogspot.com/2012/06/startup-university.html

                                              I’ll also point out that “If a worker fails, is restarted or killed the messge will be requeued and not lost.” sounds like a bad idea to me. This idea would only make sense if worker failures were unrelated to the message the worker is processing. In all likelihood though, workers fail when they can’t handle the input. In the case of a high-density DoS attack, this will just spread the attack amplifying the damage.

                                              1. 1

                                                The way JBoss handles this is to send the failed messages to a failed queue where you can do whatever you want with them. You could easily requeue with an incremented fail_count number and just log and drop the message after it fails X amount of times.