[HN Gopher] Nil in Go is typed in theory and sort of untyped in ...
___________________________________________________________________
Nil in Go is typed in theory and sort of untyped in practice
Author : pcr910303
Score : 90 points
Date : 2021-03-30 12:14 UTC (1 days ago)
(HTM) web link (utcc.utoronto.ca)
(TXT) w3m dump (utcc.utoronto.ca)
| ivanech wrote:
| This reminds me of Rich Hickey's "Maybe Not" talk
| temac wrote:
| I'm not sure why it would be significantly better to have
| reliably "typed nil" over "untyped nil". The concept has no real
| definition to begin with: what matters about sound typing are the
| static guarantees a type brings, or by association even if this
| not really the same concept, the "dynamic guarantees" about the
| behavior of objets of some "dynamic type" (that is the variable
| used as a handle to the objet had no guarenteed static type, but
| in most cases the object themselves _have_ a type). Acting on a
| "type" of nil values seems to make very little sense. Maybe there
| are obscur constructs where this would be handy, but at the first
| glance I would consider it probably indicative of a dirty hack,
| and recommend looking for alternate solutions.
| stcredzero wrote:
| In Smalltalk, _nil_ was a constant that contained the only
| instance of the class UndefinedObject.
|
| What some people did, was to put methods on UndefinedObject to
| implement a "NullObject" pattern for their domain objects.
| Since everything is default initialized to nil, everything
| would just work.
|
| The big downside of this, were beginners putting methods on
| UndefinedObject that they should not have. There's a thin line
| of conceptual understanding between a bit-bucket that hides
| errors from you, and a useful NullObject. Some people would
| call this a "dirty hack" for that reason.
| kazinator wrote:
| I'm afraid this rings slightly hollow for me.
|
| We can likewise claim about C that "0 in C is typed in theory and
| sort of untyped in practice" because constant expressions of
| integer type that evaluate to 0 can serve as null pointers of any
| type: char *p = 0; // no cast required
| if (p == 0) ... // no cast required time_t t =
| time(0); // fine char *q = 1; // error; requires
| (char *) 1.
|
| We can't think of this 0 as an object; it's just a source code
| token that provides a null pointer of the right type, which is
| statically obvious from the use.
|
| The only time it's a problem is in unsafe interfaces:
| int old_style_string_fun(); int variadic_fun(char *first,
| ...); old_style_string_fun(0);
| variadic_fun("first", 0);
|
| Here, the wrong type is inferred; the 0 just behaves like the
| ordinary constant of type int. A null pointer to char was
| intended, which requires (char *) 0 must be used.
|
| None of this challenges the fact that we can have a variable of
| type _int_ , whose value is 0.
| JeremyBanks wrote:
| C is an ~untyped language so this comparison isn't very
| helpful.
| jerf wrote:
| For some context, I've been programming in Go for about six years
| now, and I've literally never even thought about this.
|
| In general, you really haven't got much reason to be worried
| about something's "interface type", because if you're really
| worried about something's "type" (which can happen) you are
| almost certainly worried about it's "concrete type". The only
| interesting thing you can ask about the interface type directly
| would be "Does the value inside this interface implement this
| interface?", and the answer is statically yes, the compiler
| assured that, and the interface type is statically assured at
| runtime, so it's not a very interesting question. Even if fmt
| added a way to "see" the interface type passed in, I can't
| imagine what it would be useful for, because it could only ever
| be: var x SomeInterfaceType //
| some number of lines of code fmt.Printf("%I\n", x)
|
| which would statically print "SomeInterfaceType".
|
| So, I mean, it's not like anything said in that blog post is
| _false_ necessarily, but if you 're collecting reasons to hate on
| Go's nil handling, there's no real justification in adding this
| to your list. I'd also add that I answer newbie questions a lot
| on Reddit, and nothing even remotely like this has ever come up
| there, either.
| autarch wrote:
| I agree that you won't think about this too often. However, if
| you're trying to use the reflect package to get type
| information about variables, you may end up having to think
| about it (and like me, you may end up feeling very confused!).
| eptcyka wrote:
| I programmed in Go for three years, and got bitten by this at
| least twice.
|
| Being able to determine if a value that implements an interface
| is nil or not would really be nice.
| waheoo wrote:
| How does nil implement an interface?
| eptcyka wrote:
| A pointer to a type that implements an interface can be
| nil, but a function that takes said pointer as an interface
| type will not be able to distinguish between a pointer that
| points to an allocated object and a nil one.
| [deleted]
| neild wrote:
| The predeclared identifier nil is a constant, like 0 or "".
|
| Like 0 or "", nil is untyped. Go has both typed and untyped
| constants. 0 also is untyped, which is why you can write x==0
| regardless of whether x is an int32 or an int64.
|
| Unlike 0 or "", nil does not have a default type. The default
| type of a constant is the type that it is implicitly converted to
| when a type is required. For example, "x := 0" declares a new
| value x with the value 0, but 0 is untyped. Untyped integer
| constants have a default type of int, so x is given the type int.
|
| Since nil does not have a default type, it's an error to write "x
| := nil".
|
| You can declare a typed constant with the value 0 or nil.
| int32(0) is a constant with type int32 and the value 0. (*
| int32)(nil) is a constant with the type pointer-to-int32 and the
| value nil.
|
| Variables of an interface type can hold any value that satisfies
| the interface. For example, a value of type io.Reader can hold
| any value that has a Read method with the appropriate signature.
|
| The common confusion about nil occurs because you can compare an
| interface value to a non-interface value. var b
| *bytes.Buffer // b is a variable with type *bytes.Buffer
| var r io.Reader // r is a variable with type io.Reader
| r = b // r now holds a value with type
| *bytes.Buffer and value nil b == nil //
| true: b is nil r == b // true: r and b
| are equal r == (io.Reader)(nil) // false: r is not a
| nil io.Reader r == (*bytes.Buffer)(nil) // true: r contains
| a nil *bytes.Buffer r == nil // false: r
| is not a nil io.Reader
|
| The confusing part is that last line. How can r not be nil, when
| it contains a nil value? And the reason is hopefully apparent
| from the previous two lines: It depends on the type of "nil".
|
| Whether you find this a wart in the language or not, this is not
| a case of nil being either typed in theory or untyped in
| practice. The predeclared identifier "nil" is an untyped
| constant.
| stcredzero wrote:
| _0 also is untyped, which is why you can write x==0 regardless
| of whether x is an int32 or an int64..._
|
| _Since nil does not have a default type, it 's an error to
| write "x := nil"._
|
| But not an error to write "x := 0"?
| masklinn wrote:
| No, because as they note constants can have a default
| type[0]. For integer constants, that's `int`. Meaning if a
| literal integer constant is not otherwise typed, it will
| default to `int`.
|
| [0] https://blog.golang.org/constants#TOC_5.
| saghm wrote:
| The next paragraph they wrote explains this: "Unlike 0 or "",
| nil does not have a default type. The default type of a
| constant is the type that it is implicitly converted to when
| a type is required. For example, "x := 0" declares a new
| value x with the value 0, but 0 is untyped. Untyped integer
| constants have a default type of int, so x is given the type
| int."
| dhagz wrote:
| Because 0 has a "default type" of int. So when the compiler
| goes to determine the type of x, it sees its set to 0, which
| the compiler assumes to be an int.
|
| I've found thinking about nil too much in Go hurts or creates
| weird loops in my brain, so I just tend to worry about it
| when I'm dealing with interfaces or pointers. Everything else
| I'm looking at zero-values.
| stcredzero wrote:
| I'd much prefer it if in golang, the type of nil was some
| "nilType" for which != and == is defined for all other
| types. Or, define some other operation, like isNil()
|
| Then determining if something is nil could be done without
| thinking about it at all. nil would be nil would be nil.
| (Also, it would be illegal to put methods on nilType, of
| course.)
|
| (Yes, I make my living writing go, and this would make my
| life easier. And no, in that case, don't allow x := nil.)
| mh- wrote:
| exceptionally clear explanation, thank you.
| esrauch wrote:
| r==b and b==nil but r!=nil seems like the kind of thing that
| people give JS a lot of crap over.
| mirekrusin wrote:
| Can you please give more concrete example that is valid js
| but is absurd logically?
| camjw wrote:
| I think the classic example is [] == ![] evaluates to true.
| It's not absurd logically, just the operators act
| weirdly/non-standardly.
| mirekrusin wrote:
| Yeah, that one is funny > [] == []
| false > [] == ![] true
| mirekrusin wrote:
| However typescript says: This condition
| will always return 'false' since the types 'never[]' and
| 'boolean' have no overlap.
|
| ...for `[] == ![]`. Which is ironic because it does
| return true, not false. However at least it flags an
| error!
|
| I wonder how much of weirdness is wiped out by
| linters/ts/etc.
| qsort wrote:
| Honestly that's more of a weirness of the '==' operator,
| which is kind of deprecated in modern JS. On the other
| hand I fully agree that non-strict definitions of
| equality are bound to generate weird cases around nullish
| values.
| yoneda wrote:
| Huh, so equality isn't transitive in Go. Yikes.
| Spivak wrote:
| Eh, languages end up with this all the time with type
| conversions.
|
| In JS: false == undefined => false
| false == null => false undefined == null => true
| masklinn wrote:
| First, your comment makes no sense: a = 1
| b = 2 c = 1 a == b => false b == c
| => false a == c => true
|
| Is that surprising? Equality is transitive, inequality is
| not.
|
| Second, if you have to reach for old javascript "features"
| (or got forbid PHP) to defend a language you're in a bad,
| bad place.
|
| Third, the broken equality is not something that's usually
| praised about javascript. In fact the first rule of
| javascript equality is that there is only one situation in
| which `==` is the correct operator: checking that something
| is null (because that avoids having to differentiate
| between null and undefined, papering over one javascript
| wart using another). In every other situation you want
| "strict" (===) equality.
| virtue3 wrote:
| I managed to get my team to use `lodash.isNil` because we
| had gotten bitten by this crap too many times. And
| `lodash.isEmpty` for strings/arrays/etc.
| mirekrusin wrote:
| What is absurd about this statement from logical point of
| view? A != B A != C B == C
|
| ...looks like valid logical statement, ie. A = 1, B = 2, C
| = 2: 1 != 2 1 != 2 2 == 2
| dragonwriter wrote:
| That's not an example of nontransitivity. You've only shown
| that some nontruthy values are unequal by == and some are
| equal by ==. Nontransitivity would be a == b, b == c, c !=
| a. But you have the much more expected b != a, b != c, c ==
| a.
|
| JS loose equality is intransitive, though; the standard
| example being: '0' == 0; // true 0 ==
| ''; // true '0' == ''; // false
| siebenmann wrote:
| The Go specification is careful to not call 'nil' a constant,
| and in fact at one point specifically says that it isn't
| ("Conversions", in the section on converting constants into
| typed constants, which actually uses '(*int)(nil)' as an
| example of something that is not a typed constant). Also,
| although it wasn't clear in the entry, the original article
| that it was a reaction to talked specifically about 'nil
| variables' (ie, variables with the value of nil).
|
| (Even the concept of 'the value of nil' is tricky in Go; I
| believe the specification only talks about things being
| comparable to nil or allowing nil to be assigned to them. The
| specification really goes to a lot of work to not treat nil as
| a value, exactly. I suspect that the Go spec authors really did
| not want a rerun of the C idea that the NULL pointer is '0' and
| has an all-zero value and so on.)
|
| (I'm the author of the linked-to entry.)
| morelisp wrote:
| > Like 0 or "", nil is untyped. Go has both typed and untyped
| constants... Unlike 0 or "", nil does not have a default type.
|
| So it feels like you're trying to draw some distinction here
| that is like, "look, nil isn't really that special - there's
| groups A and B, and the core confusion is people think nil is
| in group A while the truth is in it's group B." And it's true,
| Go has both typed and untyped constants.
|
| But as far as I know, nil is the _only_ untyped constant
| without default type. So "group B" is still just nil, and nil
| remains a sui generis source of confusion in Go.
| 13415 wrote:
| Yes, but giving nil a default type would be way more
| confusing.
| morelisp wrote:
| Go already kind of does though, when you use it in a type
| switch nil has type nil. For binding purposes,
| `interface{}` also seems reasonable. (Or only half-joking,
| if I wanted maximum pragmatism for minimal clarity, nil's
| default type should be error.)
|
| It seems the root problem is that interfaces and concrete
| values occupy the same semantic positions. But that's way
| too hard a problem to fix; if I had a magic wand to update
| all existing code I'd probably just have `== nil` on an
| interface check for both kinds and try to rescue
| transitivity.
| brundolf wrote:
| At the risk of inviting a flame-war, it's distressing to
| me that a language this recent, with such a purported
| focus on simplicity, has design-problems this fundamental
| 13415 wrote:
| Yes, that sounds like a flame war invitation. Issues with
| nil plays no role in practice.
| jerf wrote:
| I have a hard time classifying this as a "design problem"
| when it has zero practical implications. I've never
| encountered a problem that can be traced back to this,
| nor ever helped anyone else with a problem that can be
| tracked back to this. If it is a "design problem" it is
| of the weakest possible class. I can't imagine what would
| "fix" it that wouldn't be a larger design problem of its
| own.
|
| The whole "confused about nil interface vs. interface
| containing nil pointer of some type", while I would
| contend has a root cause of improperly importing the idea
| from C++ that nil is automatically invalid when in fact
| it is perfectly valid in Go to call methods on nil and
| thus confusing "nil" with "invalid", is at least a cause
| of real bugs and real misunderstandings.
| morelisp wrote:
| Sorry, you won't get much agreement from me on this.
| There's only two kinds of languages, the kind with
| fundamental design problems and the kind people don't
| use.
|
| If you are looking for the perfect language may I suggest
| Forth on a Z80? Otherwise there's going to be a pile of
| shit hiding somewhere.
| brundolf wrote:
| I disagree. Issues like this on really core language
| primitives usually crop up when either:
|
| 1) The language lacked hindsight in its domain because
| there wasn't a ton of prior art (C)
|
| 2) The language didn't have enough foresight put into it
| (JavaScript)
|
| 3) The language's modern usage has drifted significantly
| far from its original usage (Java)
|
| Python is an example of a language people use that
| doesn't suffer from #1 (thanks to Perl and friends),
| doesn't really suffer from #2 (at least, Python 3) and
| doesn't (yet) suffer too badly from #3. It has plenty of
| flaws and limitations, but not in its foundation. The
| core language primitives are rock-solid.
|
| #1 doesn't apply to Go, and it's not old enough for #3 to
| apply. So that seems to only leave #2.
|
| (I should add that this doesn't make a language
| completely invalid or useless. I use (and even like!) JS
| despite its flaws; in fact I prefer it to Python overall,
| mostly due to the ecosystem. It's just disappointing to
| see issues of type #2, because they seem so avoidable.)
| 13415 wrote:
| It's pointless discussing this. There is no perfect
| language and every programming language I've ever seen
| has design problems at one level or another. I know
| enough of CL, Pascal, Modula, Ada, Rust, Nim, etc., to
| know their weaknesses, too.
|
| Your C example is also not convincing. Lisp and Algol
| were already strong contenders at the time. People chose
| C because they needed a fast and close to metal language
| to create their own operating system for mainframes. It
| was a hack just like your category 2. Still, it's a great
| language.
| brundolf wrote:
| > There is no perfect language
|
| > It's pointless discussing this
|
| I don't see how the first implies the second. This is a
| programming forum; it's good to discuss and debate the
| specifics of these things.
|
| C may not have been the best example of the category, but
| I think the category stands. Rust for example, much as I
| love it, is exploring uncharted territory. I wouldn't be
| surprised if 10 years from now we have ownership-based
| languages that are much easier to use, simply because
| lessons will have been learned by that point. Arguably
| Java's OOP fundamentals are not as good as C#'s because
| Java had to explore that territory first (setting aside
| technicalities about what the term "OOP" really means).
| Etc.
| 13415 wrote:
| The problem is that decades of experience has shown that
| only few programmers can discuss the advantages and
| disadvantages of programming languages for certain
| purposes in a halfway objective manner, and since few of
| them implement their own languages even the good
| discussions often remain fruitless.
|
| I'll recommend Reddit's r/ProgrammingLanguages, as well
| as Scott's _Programming Language Pragmatics_ as a reading
| instead. It 's not fully up-to-date but I found the
| comparisons of features and their trade-offs in it
| fascinating.
| nyanpasu64 wrote:
| I think Python suffers from #3, as they've been adding
| new (controversial) syntax like typing, the := operator,
| and pattern matching. These were intended to help write
| real-world code at the cost of simplicity and
| applicability as a teaching language.
| [deleted]
| miga wrote:
| It is very confusing that equality in JavaScript is not
| transitive: https://imgur.com/5pFXFbR.
|
| Apparently equality in Go is no longer transitive either...
| nicoburns wrote:
| JavaScript is fine so long as you stick to === rather than ==.
| At work, == is completely banned and tjis is enforces by CI.
| jerf wrote:
| I think I'd need to see an example of that to be sure. What's
| talked about here doesn't really lead to that, because in these
| examples, values are getting passed through a function call
| boundary.
|
| There's a lot of rules in Go for what == does, and maybe
| there's something non-transitive in there, but I'd definitely
| need to see someone prove it on https://play.golang.org/ to be
| sure. Offhand I can't think of how to make one.
| morelisp wrote:
| There is a nontransitive example heading this very comment
| section: var b *bytes.Buffer var r
| io.Reader = b fmt.Printf("r == nil: %v; b == nil: %v;
| r == b: %v\n", r == nil, b == nil, r == b) r ==
| nil: false; b == nil: true; r == b: true
| jerf wrote:
| Thank you, I missed that.
| cratermoon wrote:
| It's not confusing if you understand that r is pointer that
| points to something and b is that pointer, which is nil. So
| of course r (the variable containing a pointer) isn't nil,
| b (the pointer) is nil, and r is the pointer b.
| Dylan16807 wrote:
| Pointers don't normally compare equal to the thing
| they're pointing to.
|
| If it's acting as a proxy it's confusing the specific way
| that some things get proxied and other things don't.
| macintux wrote:
| Much discussion about nil in Go just yesterday:
|
| https://news.ycombinator.com/item?id=26635529
___________________________________________________________________
(page generated 2021-03-31 23:01 UTC)