|
| 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) |