[HN Gopher] Golang: Know Your 'Nil'
       ___________________________________________________________________
        
       Golang: Know Your 'Nil'
        
       Author : piinbinary
       Score  : 96 points
       Date   : 2021-03-30 15:12 UTC (7 hours ago)
        
 (HTM) web link (jeremymikkola.com)
 (TXT) w3m dump (jeremymikkola.com)
        
       | asplake wrote:
       | Leaky abstraction?
       | 
       | https://en.wikipedia.org/wiki/Leaky_abstraction
        
       | Floegipoky wrote:
       | Including null in golang was a mistake, but that decision is a
       | lot more defensible than the decision to call it nil. Seriously,
       | can we just pick 1 name and stick to it?
        
         | sugarkjube wrote:
         | iirc nil has its roots in the pascal/modula/oberon family of
         | programming languages which one of the go designers had quite a
         | background in.
        
         | hypertele-Xii wrote:
         | nil is Latin. null is English/French. They both mean the same
         | thing - "nothing". As long as people speak more than one
         | language across the globe, there will be multiple words for the
         | same things.
         | 
         | ...Get over it?
        
           | Floegipoky wrote:
           | 1. I can't think of any other keyword in golang that comes
           | from Latin. It's a dead language. 2. The meaning of null is
           | well-understood by the CS community. The meaning of nil is
           | not; it seems to be redefined by each language that includes
           | it. For example, nil is the empty list in Scala.
        
             | philosopher1234 wrote:
             | In short: who cares?
        
             | Smaug123 wrote:
             | "interface", "import", "struct", "constant", "defer",
             | "func", "select" are all directly Latin words, truncations
             | of Latin words, or obvious portmanteaus of Latin words.
             | "default" is ultimately from Latin but a bit less
             | immediately so.
             | 
             | This list is by no means exhaustive.
        
             | hypertele-Xii wrote:
             | > I can't think of any other keyword in golang that comes
             | from Latin.
             | 
             | Yet you probably know more Latin from programming than you
             | _think_ you do. Did you know that _integer_ is Latin?
             | 
             | > It's a dead language.
             | 
             | "In fields as varied as mathematics, physics, astronomy,
             | medicine, pharmacy, biology, and philosophy Latin still
             | provides internationally accepted names of concepts,
             | forces, objects, and organisms in the natural world."
        
       | rvcdbn wrote:
       | Article incorrectly claims that calling a method on a nil value
       | results in an error. Only attempting to dereference the pointer
       | does. It's fine to call methods on nil and that's part of the
       | "make the zero value useful" philosophy.
        
         | andreygrehov wrote:
         | Yes. That is correct. Here is an interesting piece of code.
         | This will work without an error.                   package main
         | import (             "fmt"         )              func main() {
         | foo := NewFoo()             foo.sayHello()         }
         | type Foo struct {         }              func NewFoo() *Foo {
         | return nil         }              func (f *Foo) sayHello() {
         | fmt.Println("Hello, World")         }
         | 
         | In Go, the function to be called by the Expression.Name()
         | syntax is entirely determined by the type of Expression and not
         | by the particular run-time value of that expression, including
         | nil.
        
           | [deleted]
        
           | cmckn wrote:
           | That fact that calling a function on a type does not
           | inherently dereference the pointer seems...nuts. Do gophers
           | routinely check if the pointer is `nil`? If the function does
           | not utilize the pointer (like your example) and so avoids an
           | error, the function probably doesn't need to be defined on
           | the type in the first place. This seems like a bug-prone
           | rough edge to me, coming from Java.
        
             | kitd wrote:
             | _That fact that calling a function on a type does not
             | inherently dereference the pointer seems...nuts._
             | 
             | Only if you come from a vtable-based OO language. Go isn't
             | one, so while it looks unconventional, it is rational.
        
               | kroltan wrote:
               | On one hand, it is rational if you think of Go functions
               | "on a type" as just having an extra argument, but then
               | one might ask, why bother with the special syntax at all?
               | 
               | Just go the C way and if you want to take a "self",
               | actually take it as a regular parameter. That is: (sorry
               | if the syntax is wrong, I have never used Go)
               | func (f *Foo) sayHello() { ... }
               | 
               | Would become                   func sayHello(f *Foo) {
               | ... }
               | 
               | Heck, if you want to keep the value.sayHello() syntax,
               | you can, which would still allow you to build fluent
               | interfaces or whatnot, with the bonus point of being
               | UFCS!
        
             | andreygrehov wrote:
             | Yea, you are correct in your understanding, but such code
             | is super rare in my experience. In fact, until recently I
             | didn't even know such behavior exists at all. Answering
             | your question, checking if a pointer is `nil` isn't that
             | common among gophers. A more common idiom is to return both
             | a pointer and an error, and then check if error is not
             | `nil`:                   user, err := users.FindByID(id)
             | if err != nil {             return nil, err         }
             | 
             | If the return value is just a pointer, in my books a nil
             | value should be avoided. I guess what's not cool here is
             | that there are no guarantees. If I'm using a library in
             | which a function returns just a pointer, I sometimes jump
             | in the function's body to check if it ever returns nil so
             | that I don't have to pollute my code with `if` statements.
             | Like, you almost never check for `nil` when dealing with
             | simple factories, eg foo := NewFoo(). However, when it
             | comes to a more complex method calls, it might be safer to
             | add a quick `nil` check and forget about it.
        
             | foobiekr wrote:
             | This property is actually great for making chains of calls
             | where any of the intermediate objects may be missing,
             | allowing you to short circuit.
             | 
             | x = A().B().C().D().E().F()
             | 
             | for example. Very useful when dealing with big complex
             | trees.
        
         | morelisp wrote:
         | > It's fine to call methods on nil
         | 
         | It's fine to call methods on _a concrete nil_. But...
         | 
         | > that's part of the "make the zero value useful" philosophy.
         | 
         | ... the zero value of an interface is not a concrete nil! And
         | if the interface does contain a concrete nil, it's no longer
         | nil itself!
         | 
         | If the goal was to make zero values useful, Go should have
         | rather have offered Obj C-style messages to truly nil
         | interfaces - nothing happens and all values returned are zero.
        
           | fshbbdssbbgdd wrote:
           | > If the goal was to make zero values useful, Go should have
           | rather have offered Obj C-style messages to truly nil
           | interfaces - nothing happens and all values returned are
           | zero.
           | 
           | IMO this is the worst thing about Obj C. The number of times
           | I've discovered code that was silently failing in some Obj C
           | project.....
        
             | morelisp wrote:
             | Now imagine it silently failed half the time, depending on
             | whether or not the nil had crossed an otherwise innocuous
             | function boundary. That's Go.
        
               | philosopher1234 wrote:
               | I don't understand how you can characterize Go as
               | silently failing half the time? Can you give an example
               | of this "silent arbitrary failure"?
        
               | morelisp wrote:
               | It's literally the whole thread man. Boxed nils are nils-
               | but-also-not-nils, and knowing if something gets boxed
               | means knowing whether the exact function parameter
               | signature (in or out) is an interface or concrete, not
               | just whether what you are returning is of the appropriate
               | type.
               | 
               | (And Go has no special syntax for interface vs. concrete
               | types in parameters, so you not only need to read the
               | signature but the definition of the type.)
        
               | philosopher1234 wrote:
               | I read the thread, but no one ever explained why this
               | matters or would come up in the real world? Just a lot of
               | bluster
        
       | beeforpork wrote:
       | > Tony Hoare apologized for inventing the null reference: I call
       | it my billion-dollar mistake.
       | 
       | Have we learned nothing? Many languages without
       | NIL/Nil/nil/null/NULL existed already when Golang was born.
       | 
       | And this attempt to heal the damage hurts, too:
       | 
       | > "make the zero value useful" philosophy
       | 
       | This is like instead of programming by exception (Java), it's
       | programming by ignorance (of errors).
       | 
       | I like a few things about Golang (handling of numeric literals,
       | for example), but not this thing.
        
         | kgeist wrote:
         | If they required values to always be initialized, that would
         | either require a concept of constructors (the whole OOP thing
         | they tried to avoid), or require all struct fields to always be
         | explicitly initialized at every instantation (which can get
         | awkward), or allow specify default field values (which sounds
         | like implicit magic Go tries to avoid too). In Go, by-value
         | semantics is more common than in Java with its mostly by-ref
         | semantics, so nil is less of an issue. It's also easier to
         | implement (the allocator already memsets to zero for safety) So
         | it's just a combination of factors, they took a bunch of
         | shortcuts. Not defending it, but their choice makes sense, too.
        
         | dgellow wrote:
         | My experience writing Go is that nil pointer expectations are
         | really not that big of a problem in practice. Linters and unit
         | tests are quite good at catching those.
        
         | rowanG077 wrote:
         | Well Go's goal is a lowest common denominator language with the
         | least amount of features possible and the easiest to learn for
         | new graduates. This essentially makes it almost a toy language
         | in fancy clothes. A toy language with the money of google
         | backing it so it does actually feel useable.
        
           | kccqzy wrote:
           | > the easiest to learn for new graduates
           | 
           | And I'd say it fails at that goal, judging by the number of
           | gotchas in fundamental concepts of the language like slices
           | or interfaces.
           | 
           | A surprising behavior of slices that had discussions on HN,
           | and something I discovered in my own code as well:
           | https://news.ycombinator.com/item?id=9555472
           | 
           | And of course this gem was on HN just yesterday:
           | https://news.ycombinator.com/item?id=26631116
        
           | sugarkjube wrote:
           | > easiest to learn for new graduates
           | 
           | This was never a goal of go. Go was conceived out of
           | frustration with c++. They wanted to reduce language
           | complexity and build times (among other things). For me
           | personally it's C without the hassle.
        
             | rowanG077 wrote:
             | This is false. Here it is straight from the creator of the
             | language: https://www.youtube.com/watch?v=uwajp0g-bY4
        
         | ben509 wrote:
         | I think nil/null is a symptom of the real issue: the language
         | permits partially constructed data types, so it has to assign
         | _something_.
         | 
         | That's a consequence of the "always mutable" model whereby the
         | responsibility of initialization can be shared by the object
         | constructor and the caller.
         | 
         | But there are many cases where it's very intuitive for the
         | caller to set up an object, especially in the objects as actors
         | model.
         | 
         | I think to make that work, you'd want to track the evolution of
         | the object's state, especially noting when all fields have
         | defined values.
        
           | frenchy wrote:
           | For what it's worth, it's more ceremony, but you can work
           | fine with partially constructed data types without null, it's
           | just that the nature of their construction either needs to
           | encoded int the types directly, or they need to be initially
           | constructed with defaults and you need to decide if you want
           | them to explode/panic if they're not completed, or just
           | return potentially unexpected defaults.
           | 
           | I've seen all these approaches in Rust.
        
         | gameswithgo wrote:
         | Yeah it is a strange choice, maybe just reflecting what the
         | creators of Go are used to. They have always had null and
         | haven't had any problems with it, why change?
         | 
         | Null made since in the early days of C, an option type back
         | then would have been prohibitively expensive. Today, not so,
         | any language that can afford garbage collection, the cost of an
         | option type is lost in the noise, and modern compilers can
         | often optimize the cost away entirely. (though that would hurt
         | Go's fast compile times a little bit)
        
           | iudqnolq wrote:
           | Rust has a zero-runtime-cost Option<T>. Presuming T is a non-
           | nullable pointer it compiles to something that uses the
           | nullpointer to store the None varient. (This isn't a special-
           | case for Option in the compiler, this optimization applies to
           | any two-varient enum where one varient can store a pointer
           | and the other varient stores nothing).
        
         | mdasen wrote:
         | I think the idea of "useful zero values" in Go is a mistake
         | from bias grown out of being at Google. Protocol Buffers
         | implement default zero values
         | (https://developers.google.com/protocol-
         | buffers/docs/proto3#d...) and there's no way of discerning
         | whether something is "false" or "unset", the empty-string or
         | unset, etc. In that context, it makes perfect sense for Go to
         | have similar behaviors for zero values.
         | 
         | In some respects, you can get around this by simply adding
         | another field. You can have `bool over18; bool over18IsSet` and
         | I'm guessing that Google's internal usage of protobufs does
         | this.
         | 
         | In a certain way, even getting rid of null/default values
         | doesn't fix all problems when it comes to things like updating
         | data. Think about updating a record where a field could be set
         | or unset - let's say a person's age could be a number or empty.
         | If I want to send a request updating their name, maybe I send
         | `{"name": "Brian"}` because I don't want to update the other
         | fields. How do I unset Brian's age? `{"age": null}` makes some
         | sense, but a Java (and many other language) deserializer will
         | have null for age with `{"name": "Brian"}` too. I mean, the age
         | field has to be set to something in the Java object. You could
         | manually read the JSON, but that's janky and brittle - and hard
         | in terms of interoperability with libraries and languages.
         | 
         | Maybe Google's protobuf designers would argue, "you really need
         | to have explicitness around your values and forcing defaults
         | means forcing engineers to deal with things explicitly."
         | 
         | I don't think I agree with that. I don't like Go's nulls and
         | default values. I think most languages are moving away from
         | that kind of behavior with other new languages like Kotlin and
         | Rust going against it and older languages like C#, Java, and
         | Dart trying to bolt on some null-safety (Java via the
         | `Optional` object and C# and Dart via the opt-in `TypeName?`
         | similar to Kotlin). It's possible that this is a wrong
         | direction chosen by many languages. We've seen bad programming
         | language fads before. In this case, I think we're on the right
         | track and Go's on the less-great side.
         | 
         | Go has a lot to like, but this is one of those odd decisions. I
         | understand why they did it. Go comes from Google where Protocol
         | Buffers have similar default-value behavior. I think Go would
         | be better if it had made some different decisions in this area.
        
           | cle wrote:
           | The only apt comparison IMO is Rust, the other languages with
           | JIT compiling runtimes aren't really useful comparisons.
           | 
           | To me this looks like a discussion about syntax and
           | ergonomics. Go provides the same mechanisms:
           | 
           | * for safe dereferencing, use "val, ok := *valRef"
           | 
           | * for potentially-unsafe dereferencing, use "val := *valRef"
           | 
           | Every language in that list has equivalent mechanisms. In
           | Rust you can use one of these methods: https://doc.rust-
           | lang.org/std/option/enum.Option.html or pattern matching.
           | That's a whole lot to pick from.
           | 
           | But Rust also makes it more complicated to reason about your
           | memory, see for example https://doc.rust-
           | lang.org/std/option/#representation
           | 
           | So, given Go's design tenets, using pointers makes a lot of
           | sense to me. It is easy to reason about them in terms of
           | memory and resource consumption, there are only a few ways
           | they can be used, pass-by-value semantics further reduces the
           | centrality of them, and they don't require a JIT compiler to
           | be efficient.
        
             | deepsun wrote:
             | > the other languages with JIT compiling runtimes aren't
             | really useful comparisons
             | 
             | Interesting side-point re. language comparisons I noticed
             | recently -- Java is often benchmarked together with
             | compiled languages, although I would say it's only half-
             | compiled (to bytecode, not to machine code). That's
        
             | gameswithgo wrote:
             | Why does whether a language is JIT or not make any
             | difference? C# is usually jitted but you can AOT compile it
             | just like Go if you want, and you could JIT go if you want.
        
             | iudqnolq wrote:
             | An Option<T> is literally a pointer to T with some compile-
             | time semantics. Which part of the memory model is hard to
             | reason about?
             | 
             | Edit: Nevermind, I forgot this is only of T is a reference.
             | Otherwise it's layed out like a normal enum.
        
               | cle wrote:
               | Yes this is basically what I was talking about. It
               | becomes pretty tricky to understand memory layout if you
               | try to masquerade as a regular type (hence my apparently
               | controversial reference to JIT compilers). I guess my
               | point is that Go pointers are language primitives for
               | that reason, and they support the fundamental "safe" and
               | "unsafe" access operations that all those other languages
               | have. So I don't think there's anything fundamentally
               | different between the safety of Go pointers and optional
               | types, but they _are_ easier to reason about from a
               | memory model perspective (they are laid out in memory
               | exactly as you would expect).
               | 
               | Relatedly, in practice, a lot of Rust code I've worked
               | with is littered with unwrap() calls.
        
           | jrockway wrote:
           | I don't think it has anything to do with protocol buffers,
           | but the behavior derives from the same intrinsic motivation.
           | 
           | If you don't have a zero value, a programmer has to pick one.
           | What are they going to pick? Probably what the language picks
           | for you, "int n = 0;", 'string foo = "";', etc. For a
           | language, it doesn't really matter which side you pick (force
           | programmers to select a value, or auto-assign one). For
           | network protocols, defining empty is an important
           | optimization -- if the client and the server are guaranteed
           | to agree, you don't have to send the value. This is
           | especially important where the client and the server aren't
           | released at exactly the same time; the server may have a new
           | field in the Request type that the client doesn't fill in.
           | With a predefined zero value, it doesn't matter. (You can
           | always add fields to your message to get the same effect, if
           | you actually care. I've never seen anyone do this in any API,
           | including ones that use serialization that doesn't have the
           | concept of zero values. It's why Javascript has the ?.
           | operator!)
           | 
           | Finally, Go came out in the proto2 era, which did have the
           | concept of set and unset fields (and let the proto file
           | declare arbitrary default values). Honestly, I wrote a ton of
           | Go involving protos at Google, and never saw proto3 until
           | after I left Google.
        
             | hedora wrote:
             | > _For a language, it doesn 't really matter which side you
             | pick (force programmers to select a value, or auto-assign
             | one)._
             | 
             | Even (modern) C/C++ handles this aspect of memory safety
             | better: There is no default value, and reading from an
             | uninitialized value is a compile time error (usually,
             | because C/C++ have baggage).
        
             | pyrale wrote:
             | > If you don't have a zero value, a programmer has to pick
             | one.
             | 
             | Most languages without null have an optional type which is
             | used exactly for that. In these languages, this means the
             | None value exists when you have optional things, but that,
             | then, the compiler forces you to check whether your value
             | is set when you want to use it. Serialization libraries
             | then get the choice to handle these optional values as they
             | wish, which can be not to send a field.
             | 
             | It's one of those things that may be hard to think about
             | from an external pov, but it works just fine.
             | 
             | From users of these languages, proto2 was okayish, and
             | proto3 was a massive regression. Another thing that's
             | missing in protobuf is the ability to define union types.
             | That's one frequently asked feature from typed functional
             | languages for serialization protocols.
        
               | jrockway wrote:
               | For programming languages, I agree with you. Though I
               | don't really see how "int*" is different from
               | "Optional<int>". You can write:                   func
               | foo(maybeInt *int) {           if maybeInt == nil {
               | panic("not so optional!!!!") }           ...         }
               | 
               | Just as easily as:                   func foo(maybeInt
               | Optional<int>) {            switch maybeInt {
               | case None:               panic("not so optional!!!!!")
               | ...            }         }
               | 
               | To me, it just isn't a big deal. Your program is going to
               | crash and return unexpected results when it expects
               | something to exist and it doesn't, and the type system
               | won't save you. (Even Haskell crashes at runtime when a
               | pattern match doesn't resolve. Don't see how that's any
               | different than a nil pointer dereference. Your live demo
               | is ruined.)
               | 
               | For protocols,an Optional type just pushes the problem
               | one level down. Is the optional value "None" because the
               | client didn't know the field existed, or because they
               | explicitly set it to "None"? You can't tell.
               | 
               | I think rather than going 3 levels deep, it's easier to
               | just define the default values and not distinguish
               | between these three cases. If you want an Optional value,
               | you can make yours as complex as you wish:
               | 
               | message Optional { int value = 1; bool
               | empty_because_the_user_said_so = 2; bool
               | client_has_version_of_proto_with_this_field = 3; }
               | 
               | Now if you get (0, false, false), you know that's because
               | the client is outdated. If you get (0, false, true), you
               | know that's because the user didn't feel like sending a
               | value. And if you get (0, true, true), you know the user
               | wanted 0. (Of course, there are all the other cases that
               | you have to handle -- what about (1, false, true), or (1,
               | true, false)?)
               | 
               | I think you'll find that nobody but programming language
               | purists want this feature. If your message is:
               | 
               | message FooRequest { int foos_to_return = 1; }
               | 
               | You do the right thing regardless of whether the 0 in
               | foos_to_return is what the user wanted, something the
               | user forgot to set, or the user has an old version of
               | FooRequest ("message FooRequest{}").*
        
               | travv0 wrote:
               | func foo(maybeInt Optional<int>) {            switch
               | maybeInt {            case None:               panic("not
               | so optional!!!!!")            ...            }         }
               | 
               | This function would never take an Optional in real life
               | if it's just going to crash on None so I'm not sure what
               | you're getting at here. The benefit of using
               | Optional/Maybe is that you're encoding at the type level
               | whether it makes sense for a given variable to be able to
               | be nothing, and if it does make sense, the compiler makes
               | sure you check whether it's nothing or not, but this is
               | an example of where that doesn't make sense so the type
               | should just be int instead of Optional<int>.
        
           | wruza wrote:
           | >unset age
           | 
           | In my horrible opinion, we shouldn't ditch null. We must
           | introduce null flavors (subclasses) instead and fix our
           | formats to support these. One null for no value, one for not
           | yet initialized, one null for unset, one for delete, one for
           | type's own empty value, one for non-single aggregation (think
           | of selecting few rows in a table and a detail field that
           | shows either a common value, or a "<multiple values>" stub -
           | this is it), one for SQL NULL, one for a pointer, one for
           | non-applicable, similar to SQL. Oh, and one for not-there-
           | yet, for async-await (a Promise in modern terms). These nulls
           | should be enough for everyone, but we may standardize few
           | more with time. Seriously, we have three code paths: normal,
           | erroneous and asynchronous. Why not have a hierarchy of
           | values for each?
           | 
           | Semantically all nulls must be equal to just "null" but not
           | instanceof null(<other_flavor>).
           | 
           | Edit: thinking some more, I would add null for intentionally
           | unspecified by data holder (like I don't share my number,
           | period), null for no access rights or more generic null for
           | "will not fetch it in this case". Like http error codes, but
           | for data fields.
        
             | jolux wrote:
             | Most people start using Result/Either[0] when they need to
             | define a reason for a value being missing. Then you can
             | decide how to handle arbitrarily different cases of failure
             | with pattern matching, or handle them all the same. The
             | error types themselves are not standardized as far as I
             | know, but I'm not sure how useful it is to standardize
             | these differences at the language or standard library
             | level. Is the theory that people don't use the Result type
             | correctly as is?
             | 
             | [0] https://doc.rust-lang.org/std/result/
             | https://caml.inria.fr/pub/docs/manual-
             | ocaml/libref/Result.ht... https://hackage.haskell.org/packa
             | ge/base-4.15.0.0/docs/Data-...
        
             | Ygg2 wrote:
             | We have that in JavaScript with undefined. It's awful.
             | 
             | Here is different proposal. Let's allow people to define
             | their own types of missing values. We'll call it
             | Nullable<T> or Maybe<U>.
        
               | wruza wrote:
               | A usual Maybe(Just, Nothing) doesn't cover these use
               | cases, because Nothing is just a typesafe null as in
               | "unknown unknown". Case(Data T, Later T, None E, Error E)
               | could do. It is all about return/store values, because
               | you get values from somewhere, and it's either data of T,
               | promise of T, okayish no value because of E, or error
               | because of E. Where E is a structured way to signal the
               | reason. No other kinds of code paths exist, except
               | exceptions, it seems. (The latter may be automatically
               | thrown on no type match, removing the need for try-catch
               | construct.)
        
               | Ygg2 wrote:
               | My point is, there is no size fits all. Maybe you only
               | have Some(data)/Nothing. Maybe you have a
               | Some(data)/NoData/MissingData/Error(err)/CthuluLivesHere.
               | 
               | It's better you develop one for you and that suits you,
               | rather than just a set of null-likes that are similar in
               | meaning, but different in semantics.
        
               | Smaug123 wrote:
               | Indeed: your language needs to support the ad-hoc
               | _creation_ of these primitives in a first-class way.
               | (Which is why I still consider a typed language without
               | union types to be fundamentally crippled.)
        
             | beeforpork wrote:
             | What you want is a 'bottom' class (as opposed to 'top' =
             | Object), not null. Essentially, a class that subclasses
             | everything to indicate some problem. Look at how 'null'
             | works: the class of 'null' (whether it can be expressed in
             | a language or not) is a subclass of anything you define, so
             | you can assign 'null' to any variable of any class you
             | define. This is how 'bottom' works, if you want it as a
             | class. But you already recognise that this is not really
             | what you want: you want specialised sub-classes
             | representing errors of specific classes you defined, which
             | are all superclasses of a global bottom class.
             | 
             | Such a system can be done, but it is probably super ugly
             | and confusing. The usual answer instead is: exceptions,
             | i.e., instead of instanciating an error object, throw an
             | exception (well: you do instanciate an error object
             | here...). That works, but if overdone, you get programming
             | my exception, e.g., when normal error conditions (like
             | 'cannot open file') are mapped to exceptions instead of
             | return values.
             | 
             | The usual answer to that problem then is to use a special
             | generic error class that you specialise for your type, the
             | simplest of which is 'Optional' from which you can derive
             | 'Optional<MyType>'. You can define your own generic type
             | 'Error<MyType>', with info about the error, of course. I
             | think (please correct me if I am wrong), this is currently
             | the state of the art of doing error handling. It's where
             | Rust and Haskell and many other languages are. I've seen
             | nothing more elegant so far -- and it is an ugly problem.
        
               | wruza wrote:
               | Yeah, my gp[2][0] comment addresses okayish error values
               | with Case(...). It's interesting what do you think of
               | this type? What would a language look like if that was
               | built-in?
        
               | beeforpork wrote:
               | As I said, it will get super-ugly, and it has not been
               | done (in any language with more than 1 user), I think.
               | Why? Because you will want an error class for a whole
               | tree of classes you define, and it is not so trivially
               | clear how that should look like. A simple 'bottom' (i.e.,
               | 'null') works. But e.g. you have 'Expr' for your
               | expressions and you want 'ExprError' to be your error
               | class for that that subclasses all 'Expr' and is a
               | superclass of bottom. Now when you define 'ExprPlus' and
               | 'ExprMinus' and 'ExprInt' and so on, all subclasses of
               | 'Expr', you still want 'ExprError' to be a subclass of
               | those to indicate an error. That is the difficult part:
               | how to express exactly what you want? How does the
               | inheritance graph look like? At that point, languages
               | introduced exceptions. And after that: generic error
               | classes: 'Optional<Expr>' and 'Error<Expr>', etc.,
               | without a global 'bottom'. This forces you to think about
               | an error case: you cannot just return ExprError from
               | anything that returns Expr, but you need to tell the
               | compiler that you will return 'Optional<Expr>' so the
               | caller is forced to handle this somehow.
        
             | marcosdumay wrote:
             | It's very usual in Haskell to define some error
             | enumeration, and transit your data in `Either ErrorType a`.
             | It's not a bad way to organize your code, but there is no
             | chance at all that you'll get some universal error
             | enumeration that will be useful for everybody.
        
           | vitus wrote:
           | > Protocol Buffers implement default zero values
           | (https://developers.google.com/protocol-
           | buffers/docs/proto3#d...) and there's no way of discerning
           | whether something is "false" or "unset", the empty-string or
           | unset, etc.
           | 
           | This was one of the significant changes from proto2 to
           | proto3.
           | 
           | This was also met with much opposition internally, and
           | recently changed as of v3.12 (released last year).
           | 
           | https://github.com/protocolbuffers/protobuf/blob/master/docs.
           | ..
           | 
           | https://github.com/protocolbuffers/protobuf/releases/tag/v3..
           | ..
        
       | everybodyknows wrote:
       | >An interface is actually a fat pointer. It stores a pointer to
       | the value, plus information about the type it points to. As it
       | turns out, the information about the type is actually just
       | another pointer.
       | 
       | Internalizing this understanding was a challenge worth mastering,
       | for my own work. What helped was to make the abstractions
       | concrete in a running program, with the help of Delve's "examine
       | memory" CLI command. Similarly for slices and maps.
        
       | ben509 wrote:
       | > Writing to and reading from a nil channel blocks forever.
       | 
       | Have any experienced Go devs found cases where this behavior is
       | useful?
        
         | toqueteos wrote:
         | You can disable/pause channels using this. "Advanced Go
         | Concurrency Patterns" by Sameer Ajmani explained this back on
         | 2013.
        
         | SmooL wrote:
         | Actually yes - it implies that if the read/write executes, then
         | the read/write was successful.
         | 
         | In practice, in cases where a nil read/write could happen, a
         | default "fall through" option on a select statement is used.
         | 
         | They could have made the original interface return a possible
         | error for reading/writing, in line with regular go error
         | handling, but opted instead to use more channel-based
         | conventions.
        
           | tedunangst wrote:
           | I think the question is when would it be useful outside of
           | select. Once you block on nil channel, there's no way to
           | unblock, which doesn't seem helpful.
        
             | konart wrote:
             | They only case I can think of right away is some kind of
             | process that should not die after the execution and wait
             | until the user kills it by hand after reading through
             | execution report.
        
             | jrockway wrote:
             | Such is the danger of blocking indefinitely. I think every
             | Go programmer's first concurrent app eventually crashes
             | because it runs out of memory, memory used by goroutines
             | that are waiting for an answer that will never come. When
             | you write "foo := <-ch", you're saying "I am willing to
             | wait forever for an answer". Unfortunately, actual
             | computers in the real world don't have the resources to
             | wait forever.
             | 
             | Honestly, I see the ability to block indefinitely as a bug
             | in the language. An improved language would probably
             | "result, err := <(ctx)- ch", i.e. make it impossible to
             | block without something bounding the duration of the block.
             | (Also kill sync.Mutex. 100% of Go concurrency bugs are from
             | people mixing mutexes and channels.)
             | 
             | (As an alternative, the runtime could be smarter about
             | goroutines that have blocked forever. I am not sure exactly
             | what it would look like, but the hypothetical first Go
             | program I talk about above would probably be saved by
             | something that killed goroutines that were waiting for a
             | message from a TCP connection that is long gone. I think if
             | it were easy to do right, it would have been done, though.)
        
         | jrockway wrote:
         | Personally, I never read or write a channel without some sort
         | of time bound, so this behavior doesn't really bother me:
         | select {         case foo := <-whateverCh:
         | log.Printf("hey a foo: %v", foo)         case <-ctx.Done():
         | log.Println("the user doesn't care about the answer anymore, so
         | let's not leak the goroutine")         }
        
       | macrael wrote:
       | Go would be an infinitely friendlier language if it had had built
       | in an Optional type from the beginning. People using nil pointers
       | to indicate nil values is a scourge on the language, and pretty
       | much unavoidable given the current design.
       | 
       | My harebrained idea (obviously we can't change it) is that if nil
       | pointers didn't exist the language would be much better. Require
       | that a pointer always has to point to an address and then people
       | couldn't have built all this infrastructure using nil pointers
       | (something that rightly has to do with reference semantics) to
       | indicate nil values (something that has to do with the meaning of
       | the code)
        
         | petree wrote:
         | Having expicitly optional/nullable types is great in languages
         | which are religious about it, since they remove a lot of
         | useless branching and complexity from the code. If it's just
         | tacked on, then it just tends to look ugly, without solving any
         | problems.
        
           | bqmjjx0kac wrote:
           | Syntactic sugar makes a big difference as well, e.g. Rust's
           | `?` operator.
        
             | wagslane wrote:
             | Simplicity in Go is one of it's most loved features. For
             | the most part, there are fewer ways to do the same thing
             | and I love that.
        
               | gher-shyu3i wrote:
               | It's actually only superficial simplicity. There have
               | been good comments on HN about this issue. Just because
               | the language is "simple", it doesn't hide the complexity
               | of reality, and written code ends up being harder and
               | more verbose and more difficult to manage compared to
               | powerful languages.
        
               | philosopher1234 wrote:
               | With all due respect those comments you're referencing
               | generally don't know what they're talking about. There is
               | a lot hatred on this website for Go, which appears to
               | have more to do with "I don't understand why it's
               | designed this way, and I think all good languages should
               | look like lisp/Haskell/rust" than "this design is net
               | negative for developers".
               | 
               | Practical simplicity is all about hiding complexity.
               | Unless you're building a race car, you don't need to know
               | the differences between file handling in Linux Mac and
               | windows. It just never comes up. And when it does, it's
               | possible to peak under the hood.
               | 
               | A lot of the criticism of go mistakes "difficult to
               | write" or "not trendy" for "bad design", and again I
               | assert this is because the critics don't actually
               | understand what Go is designed for, period.
        
               | tsimionescu wrote:
               | I don't think I know of a single thing where there are
               | fewer ways of doing something in Go than there are in
               | Java.
               | 
               | There are multiple ways to declare a variable, to pass a
               | value to a function, to declare a constant, to create
               | something similar to an enum, to return errors, to check
               | for errors, to handle closing, to synchronize parallel
               | threads of execution, to initialize a struct, to create a
               | list of items. I can probably go on.
               | 
               | What are some examples where Go is simpler than Java,
               | other than its current lack of generics which has always
               | been a known-limitation?
        
               | searchableguy wrote:
               | For any complex codebase, people will build their own
               | sugar and that may differ in implementation so it depends
               | whether that is a good idea.
               | 
               | Subtle differences in similar looking code can trip
               | people and increase complexity. Fortunately, go has a
               | good standard library to compensate for some of it.
        
             | donio wrote:
             | Relative lack of syntactic sugar is one of Go's best
             | features though.
        
               | jzoch wrote:
               | I dont think thats true at all. Even the sugar they have
               | is strange: see `go` and `make`. Go has plenty of good
               | features and "lack of ergonomic faculties for common
               | programming idioms in Go" is not one of them.
        
         | wruza wrote:
         | Isn't that purely interface-equality related "issue"? With
         | Maybe's you'd compare Maybe(interface(Maybe(T))) with T or with
         | Maybe(T) and it would either non-compile or be a broken
         | comparison again, depending on == semantics. Then we'd read on
         | muddy == semantics which is broken or disallowing for easy
         | comparison through an optional interface and forces one to
         | build ugly ladders of pattern matching in otherwise one-liner
         | lambdas.
        
       ___________________________________________________________________
       (page generated 2021-03-30 23:01 UTC)