
Typed nils in Go 2 - nie
https://dave.cheney.net/2017/08/09/typed-nils-in-go-2
======
pyrale
Gotta love how the initial problem (a typed language in the 2000s having nil
and empty interfaces) degenerates in workaround suggestions such as hacking
nil and the interface system to give it a special type.

~~~
ZGF4
It is interesting how Go seems to be doomed to head down the same path as
other languages it accused of being "bloated." No one intends to design a
complicated language but the reality is that the problem space is complicated.

The only thing that isn't so forgivable with Go is that these aren't new
problems this time around. Go is still struggling to get over challenges that
were first seen a long time ago. It's a great experiment on designing for
complexity vs trying to avoiding complexity.

~~~
camus2
> It is interesting how Go seems to be doomed to head down the same path as
> other languages it accused of being "bloated." No one intends to design a
> complicated language but the reality is that the problem space is
> complicated.

It's not surprising, it stems from the arrogance of Go designers who think
they can eliminate complexity by deeming it irrelevant and making the user
carry the weight of the complexity they refuse to deal with. Simplicity isn't
hard, like Rob Pike says, it's a trade off.

If you're not going to have enums in your language for instance, you are
forcing your users to implement their own, badly and in incompatible ways.

If you're not going to have generics, well you'll get this stupid situation
where users are expected to "augment" the compiler with code generators,
leading to an increase in complexity in building a program, or worse, ignoring
compile time type checking since it's the path of the least resistance when
dealing with generic container types.

------
inopinatus
The more experienced a developer I become, the more strongly (and negatively)
I feel about nils and nulls and their ilk. I have sympathy for C.A.R.Hoare who
in 2009 apologised for the apparent invention of null references in ALGOL W
(1965), calling them a "billion-dollar mistake". I've come to regard them as a
data singularity, and when I design data structures and interfaces today I am
deliberately avoiding/outlawing them; all my relational fields are NOT NULL
and I choose either meaningful defaults, or EAV or equivalents instead; in
method parameters I would rather something not exist than for it to accept a
null reference or value. And I believe that the resulting code is more
modular, more easily refactored and more reusable a result, errors are better
handled, and the resulting data structures and calling arguments more easily
interpreted, more readily queried and destructured, and are (so far) proving
generally better fitted to real-world domains.

~~~
taeric
Funny, I'm the opposite. The more experienced I've become, the more I've found
that nil-punning is ultimately what I actually wanted.

And I'm all for the idea that relational fields should be NOT NULL. I also
fear that this doesn't really work for backwards compatible thinking. If I
serialized some data down to disk before a field existed, I don't expect it to
be there when I check it later.

You can be tempted to think it should just be the zero value of the type you
are using. Or you can add some extra boilerplate around accessing. I think
either works. Just make sure you aren't getting carried away. And, try to do
anything that cares about the absence or presence of something at a layer from
where you get that something. Don't punt the decision down your codebase.

(That is, Optionals are great at the layer, don't pass them as parameters to
inner code, though. Obviously, YMMV. And, quite frankly, probably will go
further than mine.)

~~~
nerdponx
_I 'm all for the idea that relational fields should be NOT NULL_

What if the data is actually missing? How else do you record that information?

~~~
zupa-hu
You use the default empty value, and have an extra field for missingness. Than
you have real type safety.

~~~
TheDong
Real type safety is sum types. If I need to express something that is present
or missing, I should use a Maybe monad.

Having an extra field for "missingness" is less safe because the type system
won't enforce that it is either missing or set, you could have it set to a
value but marked as missing which is still ambiguous.

------
mrkgnao
Go is a lesson in how complexity can't be eliminated, only distributed
properly from the beginning so that one doesn't have to hack it in later with
messy special-casing that needs you to know how the compiler represents things
under the hood.

What happened to "lightweight typesystem that reduces cognitive load"?

~~~
ghthor
It's really easy to criticize where mistakes were made. The intention was to
make a simple language and it worked. The idea resonates with many many
engineers even ones such as I that love writing powerful pure fn code.

The intention was great and the result wasn't that great, but it still works
pretty dann well. Go is an open language and they are asking for well thought
out proposals on where & why the problems exist. Followed by ideas and/or
examples to make it better so let's all try.

I think Maybe Types would be an amazing feature to add. Closed types would
also be an outstanding win from a UX perspective. Neither of those concepts
would add more cognitive load then they remove in my opinion.

~~~
mrkgnao
> Go is an open language

From what I've seen, this holds only as long as you keep the proposals minimal
and restricted to aforesaid hacking around the limitations built into the
language. I'm happy to be shown evidence to the contrary: have there _ever_
been any proposals, reacted to in a not-completely-negative way, that were
like "uh, maybe we didn't have the right idea about <something basic>, let's
do this instead"?

I'll argue there won't be. Every community has a culture: Go's is delightfully
warm, friendly, and inclusive, but also surprisingly distrustful of learning
that there are easy-to-understand but powerful language features they could be
using to write maintainable code without "getting a PhD in type theory from
the nearest university" (to strawman a certain [type of] person [I've often
encountered when arguing about these things]).

Go has done many things right (aside from the community, good concurrency and
_really_ fast compiles come to mind) but language design is not one of them.

~~~
pjmlp
Go has done nothing new for fast compile times, like any old timer coder will
remember from Algol linage of compilers, with Turbo Pascal for MS-DOS being a
good example of how long ago those fast compile times are known.

~~~
mrkgnao
I'm sadly too young and ignorant of CS/technology history to be well-
acquainted with the "old times". (It's something I intend to fix.)

Even so, I'm all for praising the good things that Go does: if nothing,
because of the tremendous mindshare it's getting and the number of people it
reaches.

~~~
pjmlp
Codegear has kept some of the Turbo Pascal stuff on their museum site.

For example, Turbo Pascal 5.5 targeting MS-DOS was released in 1989 and was
compiling 34,000 lines/minute.

[https://edn.embarcadero.com/article/20803](https://edn.embarcadero.com/article/20803)

This is just one example, there are plenty of other languages to choose from
with a module based compilation model, only C and C++ toolchains have lousy
build times given their textual inclusion model.

[http://www.drdobbs.com/cpp/c-compilation-
speed/228701711](http://www.drdobbs.com/cpp/c-compilation-speed/228701711)

So the only achievement of Go's compilation speed was making younger
generations think it is something extraordinary.

------
maerF0x0
IMO the language made a mistake by allowing nil to satisfy any interface .
When I write a function like

    
    
       func DoStuf(i ILoveGoer) { 
         i.LoveGo() // Panic on nil
       }
    

its hard to reason about because it doesnt look like you have a pointer, looks
like you definitely have a value. IMO a nil should not be allowed for an
interface. So the only way to create an interface var is in conjunction with
assignment.

~~~
p4lindromica
this is called a bottom type in a type system. In JVM languages null and the
throw expression return the bottom type.

The only other option is to not have nil values.

~~~
kobeya
Or to encapsulate a nil in a Maybe monad, so that you only have to deal with
it in contexts where you explicity denote acceptance of nils. Then the type
system won't let you get away with ignoring the possibility of a nil.

~~~
p4lindromica
you can do a maybe monad without nil types by substituting nil with a single-
valued singleton type

------
BlaXpirit
Crystal programming language has solved the problem of nil by making it its
own type and supporting ad-hoc union types.

[https://crystal-lang.org/api/Nil.html](https://crystal-lang.org/api/Nil.html)
[https://crystal-
lang.org/docs/syntax_and_semantics/union_typ...](https://crystal-
lang.org/docs/syntax_and_semantics/union_types.html)

~~~
heythere124
Python does that too.

~~~
BlaXpirit
I thought it did not need to be mentioned, but dynamically typed languages
don't count for this comparison. Every value is like a union of every type,
and compile time type checks are impossible.

~~~
junke
Why would a language define an empty type if not for static type checking?

~~~
Veedrac
I'm confused. Nil isn't an empty type. Why are you introducing them?

~~~
junke
It goes like this: the claim is that dynamically typed "don't count for the
comparison" because "compile time type checks are impossible". Even if we
suppose that nil values or union types are useless at runtime (they aren't),
it is not true that dynamically typed languages could not be analyzed
statically. Not only compile time type checks are possible, they are sometimes
expected to happen and already part of some language's design.

Python added type hints recently, but in Common Lisp, there is not only a null
type (which contains the nil value), but also an _empty_ type: there is no
practical use in defining a type for which there is no possible value at
runtime, _except_ if the language and its type system are designed to support
static analysis. It it expected that a compiler can optimize away things that
are known in advance to be impossible, or help you detect errors statically.

~~~
kazinator
(And that empty type is the one named by the symbol _nil_.)

The _nil_ type is useful at run-time because it constitutes the bottom of the
type spindle: just like in set theory the empty set is a subset of every set,
including itself, the type _nil_ is a subtype of every type, including itself.

This can be used at run-time; e.g. _(subtypep nil 'integer) -> t_.

We can't just exclude this value from the type domain on the grounds that it's
static only. _" Sorry, you don't get a bottom plug on your type spindle at run
time ..."_. :)

------
nie
I enjoyed the blog article and I would like to gently reiterate the notion
that a _typed nil_ in Go 2 would change the semantic of _nil_, as seen in the
example expression at the end of the article:

    
    
        var b *bytes.Buffer
        var r io.Reader = b
        fmt.Println(r == nil)
    

We might need to use other expressions to capture the _nil_ type of above
assignment but we should enable the _value only_ equality check with `r ==
nil`

~~~
dullgiulio
Can someone point me to some examples where checking that the type of an
interface is nil?

I have thought a bit about it but I couldn't come up with good situations.

~~~
gondo
"the type of an interface is nil" is that even possible?

~~~
masklinn
Yes. The interface holds the concrete type of the value, if there is no
concrete type it will be nil, so if you assign a nil to an interface-typed
variable directly, you'll have a (nil, nil). If you first assign the nil to a
pointer type T then assign/convert that to an interface type, you'll get (* T,
nil). Here's a trivial demo:

    
    
        var a interface{} = nil // (nil, nil)
        var b *int = nil
        var c interface{} = b // (*int, nil)
        fmt.Println(a == c)
    

Of course most such cases are not that trivial, rather they're cases where a
function takes an interface-valued parameter and checks for (param == nil), if
the caller passes in an actual object there's no problem, if they pass in a
concrete value no problem, but if they extract the nil literal to a
concretely-typed context (variable) things go pear-shaped to various levels of
fuckedness (depending what is done in the other branch).

And that's vicious because something as seemingly innocuous as "extract
variable" on an immutable literal can break your code.

~~~
gondo
thanks for the example. so the answer to @dullgiulio question could be done by
using reflection:

    
    
        var a interface{} = nil // (nil, nil)
        fmt.Println(reflect.TypeOf(a) == nil)

~~~
dullgiulio
Thank you for your answer. I was writing from the phone and didn't make myself
clear. My question is: what are the legitimate use cases for interfaces that
are half-nil?

Ignoring the compatibility guarantee for the sake of discussion, I feel that
nobody would notice if the compiler tomorrow started short-circuiting the
equality check of interfaces against nil to return true if either tuple value
is nil. But maybe I'm missing some use-case.

~~~
masklinn
> My question is: what are the legitimate use cases for interfaces that are
> half-nil?

I think that's two different questions:

* Is there a legitimate use case for nil not being nil? I don't think so.

* Is there a legitimate use case for having "typed nil" interfaces? Kinda, Go supports and encourages calling methods "with nil receivers", and doing that through an interface requires that the concrete type (the non-nil half) be conserved otherwise you can't dispatch the method call.

------
hoodoof
This may be the "computing industry sentence of the year", if they had an
award for "sentence of the year", which I'm sure they don't. Whoever they are.

while nil is assigned to t2, when t2 is passed to factory it is “boxed” into
an variable of type P; an interface. Thus, thing.P does not equal nil because
while the value of P was nil, its concrete type was *T.

~~~
concede_pluto
This is required to support one of the most peculiar features I've ever seen--
method dispatch on the concrete type of object that isn't there.

~~~
wvh
This is where language design morphs into philosophy...

------
knocte
Yet another item to add to my list-of-reasons-of-why-not-to-use-Go. Thanks

~~~
IshKebab
If you don't use languages because they have some edge cases and minor flaws
how do you do any programming at all?

~~~
coldtea
Does that sound like an edge case?

~~~
laumars
Checking the value of a property [edit: return of method] after you've nil'ed
the parent object is enough raise an exception in most languages. So yes I'd
say that's an edge case. Where Go gets it wrong here is because _nil_ isn't
really `nil` you get a silent `false` rather than an obvious crash + stack
trace. But regardless of the bad design of Go around the usage of "nil", the
code would have failed in pretty much any other language anyway.

~~~
masklinn
You're not "checking the value of a property after you've nil'd the parent
object", you're checking if you were given a nil. This issue can occur for any
function which takes an interface-typed parameter. That's usually how it
happens: somebody passes in a `nil` which comes from a pointer-typed variable:
[https://play.golang.org/p/ADTvLDDrw6](https://play.golang.org/p/ADTvLDDrw6)

> But regardless of the bad design of Go around the usage of "nil", the code
> would have failed in pretty much any other language anyway.

No, it would not. In Java, null is null whether it's typed as a concrete
reference, as an array or as an interface.

~~~
laumars
> _You 're not "checking the value of a property after you've nil'd the parent
> object", you're checking if you were given a nil._

Sorry, it's a method not a property, but I think my point remains valid with
regards to the example in that article. Just to be clear, I'm not trying to
defend nil here, but I do think it's important to understand the issue because
I think the authors code would have failed regardless of the language. So
Let's break the code down: first they create a struct exposed via an
interface{}

    
    
        type T struct{}
    
        func (t T) F() {}
    
        type P interface {
            F()
        }
    

func newT() *T { return new(T) }

Then they create an initialized variable that object type:

    
    
        t := newT()
        t2 := t
    

...and set that interface{} to nil:

    
    
        if !ENABLE_FEATURE {
                t2 = nil
        }
    

Then they check the value returned from a method of the struct - bare in mind
this is after the struct has been `nil`ed:

    
    
        thing := factory(t2)
        fmt.Println(thing.P == nil) // returns nil
    

If there's a likelihood that they could be working with nil interfaces then
they should be first checking the value of the interface before checking the
value of the methods within it. Most OOP languages would raise an exception /
print runtime error (in the case of JIT dynamic languages) or downright crash
if you tried to access methods or properties of a nil / null / whatever type.
So I'm not defending Go's behavior but their example is peculiar to say the
least.

That all said, I do feel your examples are a lot more relevant to this
discussion than the one that prompted the discussion to begin with.

~~~
coldtea
> _Most OOP languages would raise an exception / print runtime error (in the
> case of JIT dynamic languages) or downright crash if you tried to access
> methods or properties of a nil / null / whatever type._

Most OO languages wont report an interface as non null if its value is null.
Go will.

~~~
laumars
Indeed - I wasn't defending Go's behavior here. I was just replying to your
question about whether it is an edge case or not. Since the code would be
broken in any OOP language I do consider this to be an edge case. But that
doesn't mean I like nor would defend Go's nil type system.

------
fithisux
I didn't know that you can compose a struct with an interface. Nice.

------
smegel
Good example of _code smell_.

------
lngnmn
Oh, that is truly hipster's concept - more than one nil. similar to -0 and +0.

Public cosplay of [presumed] intelligence as a new smoking.

