Live data from Hacker News

Twelve Go Best Practices

talks.golang.org

71–80 of 153 posts

Re: Twelve Go Best Practices

#71

Earlier quoted context omitted.

W/r/t #2 - you're not familiar with Go but knew exactly what was going on. That's totally a feature. The language was designed around exactly that kind of reading. "break;" is implicit in Go.

Sure, I spend a decade in C. It's not hard to read. My only problem with using generics in this context is that you can't catch type-conversion errors at compile time. Seems like a step backwards with only downside. I get why exceptions are a double-edge sword. I'm not clear on why undermining compile time type safety is an feature.

> I'm not clear on why undermining compile time type safety is an feature.

I think this is what people will ultimately focus on when considering Go. Many of the complaints are a product of type weaknesses in the language, voiced by people who had assumed that a modern static language wouldn't have that fault. Others tend not to mind because they lack that expectation, and regard the dynamic behaviour you can get as a feature. The argument about shared mutable state goes the same way, but for some concurrent but non-parallel code it might be convenient.

I can easily see people picking Go when moving from Python. But not when moving from a static language with a stronger, safer type system.

Also, as burntsushi points out, it does require more sophistication in the type system. I doubt they're trying to sell a naively simplistic type system (sophistication often makes it easier to use), but when Go was announced the feature they seemed to be selling the hardest was short compilation times. I think that feature is the seed of this behaviour.

Re: Twelve Go Best Practices

#72
post #43

Earlier quoted context omitted.

Just sending "true" as a signal is a convention, but some people prefer something like http://play.golang.org/p/oXapo7R4RX

Cool, I like that. In some ways, it would be nice if there were a friendly alias for struct{} as part of the language, but I suppose it's hard to come up with a good general name for that.

Many languages call it unit or ().

Re: Twelve Go Best Practices

#73
post #30
post #23

Interesting that this snippet: func (g *Gopher) DumpBinary(w io.Writer) error { err := binary.Write(w, binary.LittleEndian, int32(len(g.Name))) if err != nil { return err } _, err = w.Write([]byte(g.Name)) if err != nil { return err } err = binary.Write(w, binary.LittleEndian, g.Age) if err != nil { return err } return binary.Write(w, binary.LittleEndian, g.FurColor) } could be written like this: func (g *Gopher) Dum…

It's just a different (arguably better default) to have to explicitly ignore errors and/or explicitly bubble them up the call chain. You'll always be in control of your code's control flow that way. You'll never have some random library 5 levels beneath your code throw an exception that you didn't know about, causing your function to return prematurely, resulting in your function accidentally leaving some file handle…

What are the problems with checked exceptions that don't apply to go's approach?

Re: Twelve Go Best Practices

#74

Earlier quoted context omitted.

My point is that an example of a 'best practice' shouldn't make the reader think: Oh, that's a kludge to get around a design decision in the language. In his example, he uses a one-off type to isolate the caller from having to explicitly check if each individual write failed. I got no problem with that. But it seems like it a work-a-round. It's just an odd choice for an example. The take-a-way seems to be that the ba…

> My point is that an example of a 'best practice' shouldn't make the reader think: Oh, that's a kludge to get around a design decision in the language. I would think that some of the most important best practices would relate to the best means of dealing with situations where the approach users coming from other languages might naturally seek to apply are not the most appropriate, either because the other-language f…

Yeah, fair enough.

I want to like that language but I just keep going seeing things that make me pause.

Really I'd like a C-dull or a C-- (keep a small subset and get close to the metal as C)

Re: Twelve Go Best Practices

#75
post #23

Interesting that this snippet: func (g *Gopher) DumpBinary(w io.Writer) error { err := binary.Write(w, binary.LittleEndian, int32(len(g.Name))) if err != nil { return err } _, err = w.Write([]byte(g.Name)) if err != nil { return err } err = binary.Write(w, binary.LittleEndian, g.Age) if err != nil { return err } return binary.Write(w, binary.LittleEndian, g.FurColor) } could be written like this: func (g *Gopher) Dum…

If the language supported exceptions, how would you write this func? func (g *Gopher) DumpBinary(w io.Writer) { // Ignore all errors _ = binary.Write(w, binary.LittleEndian, int32(len(g.Name))) _, _ = w.Write([]byte(g.Name)) _ = binary.Write(w, binary.LittleEndian, g.Age) _ = binary.Write(w, binary.LittleEndian, g.FurColor) }

It's trivial to write a function to ignore exceptions if that's what you want.

    def ignoreExceptions[A](a: => A): Unit = try {a} catch {case _ =>}

    def dumpBinary(g: Gopher, w: Writer) = {
      ignoreExceptions binary.Write(w, binary.LittleEndian,     int32(len(g.Name)))
      ignoreExceptions w.Write([]byte(g.Name))
      ignoreExceptions binary.Write(w, binary.LittleEndian, g.Age)
      ignoreExceptions binary.Write(w, binary.LittleEndian, g.FurColor)
    }
Though honestly I think a better solution is monads.

    //returns Validation - either success, or the first error (which stops processing)
    //return values are directly accessible, because later code won't run unless earlier code succeeds
    for {
      _ 

Re: Twelve Go Best Practices

#76

if err == nil { _, err := w.Write([]byte(g.Name)) if err == nil { err := binary.Write(w, binary.LittleEndian, g.Age) if err == nil { return binary.Write(w, binary.LittleEndian, g.FurColor) } return err } return err } Why does anyone have to tell people not to do this? How does it enter anyone's mind as a thing to do in the first place? I've been known to go too far to minimize nesting. I get twitchy at the second lev…

Because the alternative is worse. If you test-and-return after every call, your function has multiple exit points and is far less maintainable. If you nest like this, you at least have a chance of maintaining a single exit point in your function (even though this example fails to do so).

This is why the Lord invented exceptions, which it seems that Go does not use. This one example is enough to convince me to never use Go for anything. What a huge step backwards.

Re: Twelve Go Best Practices

#77
post #72
post #43

Earlier quoted context omitted.

Cool, I like that. In some ways, it would be nice if there were a friendly alias for struct{} as part of the language, but I suppose it's hard to come up with a good general name for that.

Many languages call it unit or ().

Hah, I was just about to edit my comment to mention unit after doing some reading :)

Re: Twelve Go Best Practices

#78

The type cast as part of the switch is really cool, I hadn't seen that before. switch v := v.(type) { case string: w.Write(int32(len(v))) w.Write([]byte(v)) default: w.err = binary.Write(w.w, binary.LittleEndian, v) } Great way to alter control flow based on the type, without a ton of ugly casts cluttering things up.

Of all the code in there this part confused me. What exactly is being switched on? It looks like v is being reassigned to the type of v, then the type of v is written out (instead of the value).

"type" is a magic word in Go, and in that example. It's highly idiomatic -- it's inconsistent with the rest of the language (Using "type" instead of an actual type), but it makes sense once you memorize the idiom. Sort of perlish -- there are two different operations that look basically the same, and the correct one is chosen based on context (in this case, the context is "is there a type name, or "type" literally?)

Perhaps it would have been cleaner to use "*" or some other operator symbol instead of the reserved word "type"

Re: Twelve Go Best Practices

#79
post #10

"Deploy one-off utility types for simpler code" can be called a monad or Optional. I wonder if the language developers will add more formal support for that; it looks impossible to add Optional as a library due to lack of user-configurable generics.

I'm sort of hoping someone forks Go to provide at least a few single-depth generics like Optional/Maybe, to match Map and Slice.

Re: Twelve Go Best Practices

#80

if err == nil { _, err := w.Write([]byte(g.Name)) if err == nil { err := binary.Write(w, binary.LittleEndian, g.Age) if err == nil { return binary.Write(w, binary.LittleEndian, g.FurColor) } return err } return err } Why does anyone have to tell people not to do this? How does it enter anyone's mind as a thing to do in the first place? I've been known to go too far to minimize nesting. I get twitchy at the second lev…

Short circuit returns are the devil - they make it much harder to factor out part of a function into a smaller function. A function should have one entry point and one exit point; that's the whole point of structured programming. If you're going to return from some random point in the middle of your function you might as well be using goto.

(Of course, good programming languages provide a better solution than pyramid-of-doom nesting)

Post reply on HN