Hacker News new | comments | show | ask | jobs | submit login
Experiments with Ruby and Go (jorin.me)
61 points by xatxat on June 14, 2015 | hide | past | web | favorite | 29 comments

Two things jumped out at me when I switched from Ruby to Go

- Go speeds up my workflow since the compiler catches many of my errors. When I write Ruby, I'm about 75% confident it will be right first-time. With Go, that's about 95%.

- I ended up thinking about every possible error. With Ruby, edge-cases would often only crop up after a few weeks of running the code in production.

> I ended up thinking about every possible error.

Exactly. In Go you can't just wrap code in a try/catch block and not deal with the fall out. You can ignore errors with _, but you still have to explicitly write out _, so you visually see what you're ignoring.

I think this is Go's simplicity is actually helpful. It feels like boilerplate at first, but there is something to being _forced_ to handle edge cases sooner rather than later.

No, you don't need the 'try / catch' syntax, but in Go you can ignore errors.

And instead of 'try / catch', you have the frustrating 'if err != nil { return nil, nil }'.

    io.Copy(out, in)
Did you know io.Copy returns an error? It does, and I rarely see it handled. You don't need to explicitly '_' ignore things. If you're about to say 'errcheck', well, great, it's not built into the language and what matters is practice; large go codebases do not use errcheck typically. Also, errcheck only handles things that fill the error interface, which e.g. the 'bool ok' return values (such as from map accesses) do not.

With Java exceptions, at least, you must explicitly handle an exception in some way. If you don't try/catch it, you have to add the throws declaration to your method. You have to handle that case.

Furthermore, you know exactly what sort of error it is. In Go, you get the silly "error" interface. So it's something. Have fun figuring out how to handle it. It's not like the language encourages you to just return 'errors.New/fmt.Errorf' so there's actually no way short of complicated regex string matches to figure out what error case you're actually handling. You don't get given all possible types of the error, if you try to switch on err == io.EOF or err == io.UnexpectedEOF or so on, how will you know that you got them all? The compiler sure as hell won't tell you.

With exceptions, you typically do have to get them all, you do have to account for all the types (and if you add a new type further down the stack, you'll have to make sure you handled it correctly further up). You can just catch 'Exception' and be in a similar place to Go, but at least Exceptions give you the option to do things more right.

Your point makes utterly no sense to me. Between Go 'errors' and Java 'Exceptions', only exceptions a) Make you actually think about and handle every one of them b) Can give you information about all possible types it may have and c) allow you to elegantly let a parent function handle it without 3 lines of utterly boilerplate code after every single function call.

Sometimes I think that Gophers just want to feel like they're handling errors. so they love 10% of their code being utterly worthless while not actually having any common way to handle specific error types and while managing to make worse error handling then every single other modern language, and then be proud of it.

In my experience, checked exceptions in Java just means most of your functions end up marked as throwing "Exception", or if you're more disciplined, throwing "MyApplicationBaseException" which is not really much better.

The reason why errors are better than exceptions is mainly that it is clear, when reading any function, what lines can fail, and what happens if they do. When reading a java function, you have no idea if half the lines in the function throw an exception. You might know the function as a whole can throw, but you don't know where execution might suddenly stop inside the function. You have to lean on the IDE to look at the definitions of all the functions this function calls, to see if they can throw, which is not something you can do when looking on github, or in a code review, or in an email notification for a commit.

> Did you know io.Copy returns an error? It does, and I rarely see it handled. You don't need to explicitly '_' ignore things.

Correct, and I agree this isn't explicit. I'd actually like to see you _not_ be able to ignore errors like this. But that's just me maybe.

> Your point makes utterly no sense to me.

My point is Go, by it's simplistic nature, more often than not forces you to at least think about the error case, instead of punting it to another part of the code. This is the point of the parent comment which I'm in agreement with.

If you're writing 'if err != nil { return nil, nil}' you're failing on oh so many levels.

Also, Java doesn't make you think about handling exceptions any more than Go does. If we're talking about code, I've seen lots of Java where the programmer figured "oh, I'll let someone else handle these exceptions..."

It's not just the try/catch thing. Ruby code can have a syntax error in a conditional that is rarely true, which may crop up in production from time to time. I've seen it happen many times, including in some popular Ruby libraries. A compiled language would have caught that.

"Ruby code can have a syntax error in a conditional..."

No, it can't. If this is something you've seen "many times", would you mind providing an example? Show me a program that Ruby tries to execute without having been able to parse it.

    if foo.something
      foo.instance_eval "SYNTAX ERROR"
I know any form of eval is sort of cheating...

But perhaps what he meant was not a syntax error, but things like:

    if foo.something
      foo.something_misspelled = bar
Which isn't technically a syntax error, but that code is invalid in a statically typed language.

Did you mean to say "semantic error" instead of "syntax error"?

'Every possible error' could you explain what you mean by every.

Additionally how many percentage of errors relate to typing and are solved by static typing? Don't get me wrong, I have really used C, C++, Java, C# and Ruby for a lot of projects. In my experience basic "types" are reminiscent of cpu architectures; they are a small subset of categories or classes. Beyond standard library you have a lot of risks in correctly programming the business domain. So atatic types don't help me much.

Of course, you always have lots of risks in correctly programming the business domain. Because this is related to human communication, analysis, expectations and an understanding of the requirements, this is a very hard problem in software engineering, and one that cannot be solved by programming languages.

That said, static types do indeed help with other problems in software development. If nothing else, writing software using a language with static types means there will be fewer tests you should write to catch silly errors, leaving you free to focus on the truly hard parts of your problem domain; in some sense you can consider types as a kind of test the compiler automatically writes for you. Because powerful type systems can encode a lot, I'd say the answer to "how many errors relate to typing" is also "a lot". For example, do you consider a NullPointerException in production an error? I do, and I've seen plenty of them.

Also, consider that Go is probably not the best exponent of static typing out there, so you should be careful not to confuse "flaws with Go" with "flaws with static typing".

"I ended up thinking every possible error" sounds a bit bold..

Ok the more low-level a language is the more you will have control on everything, but hey, Go is not ASM and you will still risk making mistakes.

Even having just one error you didn't think of, then when it presents itself and you're coming back to the code after months, I'm not sure which language, Ruby or Go, will be more pleasant on you..

Along with some other reasons, that's why I still prefer compiled, static typed languages in general.

I'd highly recommend 7 Languages in 7 Weeks by Bruce Tate.

It doesn't have Go, but it walks you through Ruby, Io, Prolog, Clojure, Scala, Erlang, and Haskell. It will give you a wide basic understand of why design tradeoffs have been made.

Totally have a look at Haskell next. It takes a while, but it's worth it :)

Thank you for sharing, looks awesome! Can't believe this is Java code! I definitely need to checkout Java 8 at some point :)

It's funny that many people don't distinguish between language and standard (or default) libraries.

It's because the distinction isn't meaningful for day to day use. People use and compare languages and their standard libraries as a whole.

I do think people recognize the fact that these two are different things (e.g. when comparing C to a language with a richer standard library it's one of the points that are often specifically and separately mentioned.), but it's just not very useful to separate them explicitely in a comparison of languages from a user's point of view (as opposed to a language designer's point of view).

Not sure what you're talking about. Go provides No "methods" for arrays.

You have to remember all these tricks :


You can't even wrap them into functions or you'd have to write type assertions everywhere.

Now,you could define a custom Array type with methods, but goodbye type safety. It's funny how sometimes Go works better as a "dynamically typed" language than as a statically typed one.

Ruby on the other hand provides loads of methods for every single "core" type.

This is my biggest gripe with golang; I want to be able to write functional extensions for the blessed generic types (slice, map etc.), but can't. Until we can do things like this, or implement our own generic types, golang will be a niche language.

I don't think so. What you're saying is you want to apply a niche concept (fp, I know it's getting traction, but still pretty niche in the business world) to an OO language and have a hard time doing that. Golang is niche right now too, but the lack of functional ability is probably not going to to hugely affect anything.

I'd disagree that FP is niche; the concepts are used throughout business, and more and more back ends are being written in an FP language. Front ends using JS are being written more and more in an FP style as well.

anyone else actually found the go code easier to understand than the ruby one liner combining sort, maps, reverse a funny "&" syntax (of which, as a non-ruby developer, the meaning isn't really obvious) ?

Not me. I understand both code snippets, while being only passingly familiar with Ruby and not at all with Go. I can guess what the bits I'm unfamiliar with do.

Ruby's code is more declarative, and seems to use common functional idioms, which make it very easy to understand ("map", "sort", "reverse", "take" mean mostly the same in a wide variety of languages). In this case I do not need to know what the computer is doing "close to the metal", just as I do not need to know the physics of asking someone to fetch me a glass of water.

The Go snippet is also easy to understand, but it has more noise in the form of boilerplate. Here the name of the function is more helpful to understand what it's supposed to do. If the function was named GuessWhatThisDoes I'd figure what it does, but I'd have a harder time than with the Ruby version.

To be honest, both snippets can be understood, but unless I truly needed to understand the step-by-step performance implications, I'd rather read the more declarative Ruby snippet.

Well, it's not very meaningful to compare a good example of something with the bad example of something else (one lines are generally considered bad practice also for this reason).

If that line would be "optimized for readability", there would be at least one or two intermediate variables.

I would disagree and say the functional style is perfectly fine in its one-liner form. I'd even say it is preferable in many cases over using intermediate variables. Why? Because naming is hard and more often than not I see the intermediate variables named things like "sorted_histogram" or "sorted_reversed_histogram" which is hardly any clearer than "@histogram.sort_by{..}.reverse" while being much more verbose.

Intermediate variables in functional languages are largely used for memoization. Occasionally are they used for self-documentation but generally I see "people.map(&:full_name)" over "full_names = people.map{ |p| p.first_name + ' ' + p.last_name }".

For me the debugger tends to drive if I use one liners often or not. In C++ or C# it tends to be easier to step thru code if it's broken out. In Ruby I think it's probably less common to work with a source stepping debugger so it feels more natural to write nice functional one liners that do a lot.

Yet the author found the ruby code easier to understand. And I very often read praises about functional programming language's ability to chain functions in this fashion. I'm very often able to understand when two functions are chained the first time i read the line, but i very often have to read the line many times and decompose each parts above three.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact