[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)
 
web link (jeremymikkola.com)
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. 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 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". You can write:                   func
        | foo(maybeInt *int) {           if maybeInt == nil {
        | panic("not so optional!!!!") }           ...         }
        | 
        | Just as easily as:                   func foo(maybeInt
        | Optional) {            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) {            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.
 
    | 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 "" 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().
    | 
    | 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 or Maybe.
 
        | 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'. You can define your own generic type
      | 'Error', 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' and 'Error', 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' 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)