1. 33
  1.  

  2. 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.

                              2. 34

                                Summary: “I liked Ruby, now I like Go, let’s write something off the top of my head about it.”

                                1. 9

                                  I get why people might find this post uninteresting. But I like to hear why people prefer particular languages, it helps me develop my internal characterization of the language. Especially when they describe where they’re coming from like in this post. And especially when they have actual projects they’ve launched publicly.

                                2. 7

                                  TL;DR is at the bottom:

                                  For me, Go has become the new Ruby. A language I use to get things done, and enjoy programming again.

                                  I was hoping for a deeper comparison than “I personally find them both ergonomic”. It’s not a bad post, it’s just not what I was expecting.

                                  1. 4

                                    This sentence made me question the article:

                                    Another downside of using a language such as C is that the community of C developers is dying.

                                    1. 3

                                      I mean, this is true in a trivial sense in that all humans are mortal… but in the specific sense I feel that C community is coasting steady, and maybe even growing.

                                      1. 3

                                        I mean, this is true in a trivial sense in that all humans are mortal…

                                        Well, it this sense every community is dying, even the RESF. ;)

                                    2. 4

                                      Ruby is similar to Perl in that it suffers from “too many ways to do something”. Take for example “append to array”:

                                      a3.push(n1, n2)
                                      a4.concat(a1, a2)
                                      a5.insert(-1, n1, n2)
                                      a6 = [*a6, n1, n2]
                                      a7 = a7 + a1 + a2
                                      a8 << n1
                                      

                                      Do we really need 6 ways to do this? Compare with Go, which has one method (or two, depending on how you count:

                                      a2 = append(a2, n1, n2)
                                      a3 = append(a3, a1...)
                                      

                                      Another example is “invoke function”, where Ruby has at least 5 methods:

                                      n1 = f1(20)
                                      n2 = f2.call(20)
                                      n3 = 20.then(&f2)
                                      n4 = method(:f1).call(20)
                                      s1 = 20.send(:to_s)
                                      

                                      Also, Go is a compiled language and Ruby is interpreted. I cant see a situation where one replaces the other. They are used (or should be used) for different things.

                                      1. 2

                                        Something about the semantics and stylistics of Go draws Python and Ruby people to it. I don’t personally understand it, but I have witnessed it.

                                        1. 11

                                          The draw is how frictionless the development feels. In the same way that Python and Ruby feel frictionless Go also feels frictionless. The benefit is that Go tends to stay frictionless in the maintenance period for languages as well which is a property Ruby and Python have tended not to have over time.

                                          1. 2

                                            In comparison to Python; it excels at the things Python is bad at. Deployment is excellent. It’s easy to cross compile. It’s easy to compile your application to one statically compiled executable. The performance is excellent for things that Python are not fast enough to be used for. You get types, so creating larger applications that don’t collapse under its own weight is much easier than in Python. You can use types in Python, and write more tests than code, but large projects in Python are still painful. And you get the advantage of not having an ex-co-worker writing a function that generates classes, 4 years ago, and now you have to dig five layers deep for every line of source code you wish to understand completely. Or so I heard, from a friend.

                                            Go has minimalistic syntax and makes everything that has to do with other programmers much easier.

                                        2. 4

                                          Maybe trying to write this one big web service with dozens of endpoints in Go wasn’t a good idea because that’s what soured the language on me. Everything REST, typical CRUD - everything took twice as long as in Symfony or Rails (or more than twice as long) whereas before that I had enjoyed writing stuff in Go - and so far I haven’t had the chance or need to revisit it. I loved writing Nagios-compatible checks in Go, though. Easily deployable as well.

                                          1. 1

                                            If you had to do it again, would you do it again in Go, but with a better approach? Or just not use Go at all, and turn to something else?

                                            1. 2

                                              Hard to tell, of course I can’t blame it all on Go. It was a typical second system - rewriting a central (user/groups/whatever) API that is the heart of the company’s product. I hear you (“don’t rewrite”) but the old one was in an old unmaintained version of Symfony, nobody on the team wanted to progress with PHP for this project and a few other factors.

                                              Yes, the people involved were beginners with Go, but not total beginners, we had successfully written several applications (bigger and smaller) in Go, but no one had more than 1 year of experience with Go.

                                              Yes, maybe we could’ve found the magical trick that would’ve made this sort of CRUD stuff manageable, but we didn’t.

                                              In the end, the old API was too slow (but not THAT much too slow) and the data model for a few things had to change, so a rewrite in PHP would’ve been feasible but no one was excited. We were switching everything to Go, so this made sense anyway.

                                              The company doesn’t exist anymore, so my knowledge of Go hasn’t progress, so there’s not so much hindsight involved. It did work after all, it was not hugely out of proportion in a sense of “get it done to spec, on time, no critical errors” - it just never felt right. I think I would absolutely get some experienced Go developer (freelancer?) on board this time to help with sanity checks and groundwork. It was not a huge project overall, so maybe 1 month of external expertise might have helped enough already.

                                              But in the end.. not sure. What other ecosystems are there for such a problem? :) PHP was out, we were migrating away from Python (but Flask or Django would’ve worked), nobody knew Rails, Rust wasn’t ready, Java was deemed unfit…