Hacker News new | ask | show | jobs
by tikhonj 4258 days ago
Nil adds complexity because it creates a hidden failure condition in every possible value. One that's not necessary most of the time.

Option types, on the other hand, are not a source of complexity, because they just use a more general language feature (ie variants). And variants aren't a source of significant complexity because they're symmetric to records (ie structs) and so emerge naturally. Symmetry inherently simplifies design by organizing and structuring things. They're a very small step beyond enums and a much saner alternative to unions.

Nils are ultimately more complex because they're baked into the language and omnipresent. And they give you very little in return! Variants, on the other hand, are a natural and relatively simple design that also vastly increases the expressive power of the language. And gives you option types for free.

1 comments

> Nil adds complexity because it creates a hidden failure condition in every possible value.

That's simply not true in Go. `nil` is not an inhabitant of all types. Integers, floats, strings, arrays and structs cannot be `nil`. Slices, maps, functions, channels, interfaces and pointers are nilable.

> Option types, on the other hand, are not a source of complexity, because they just use a more general language feature (ie variants).

Go does not have variant types, so if adding option types requires them, you get an increase in the size of the language. In Go's case, variant types would have a weird interaction with interfaces, which arguably increases complexity.

> That's simply not true in Go. `nil` is not an inhabitant of all types. Integers, floats, strings, arrays and structs cannot be `nil`. Slices, maps, functions, channels, interfaces and pointers are nilable.

True. But beside the point. Yes some things can be nil and some things can't. Problem is that there is nothing stopping me from doing this :

  func (x *X) {
    x.boem()
  }
and that can crash. With ADTs ("option types (assuming a Haskell-like Maybe type) the compiler would complain : "x can be of type Nothing, so you can't just call a method on it".

The point is that Option types mean that you can still have optional values, but you can never have nil pointers.

> Go does not have variant types, so if adding option types requires them, you get an increase in the size of the language. In Go's case, variant types would have a weird interaction with interfaces, which arguably increases complexity.

They would have exactly the same interaction with interfaces as they would have with anything else. ADTs are not of any definite type, so you have to case select them in most cases, and you can forego nil checking for everything else.

You would have the "grouping" behavior anyway, since in Go you don't declare that you satisfy interfaces. So if all possibilities for an ADT implement the same interface, then the ADT should magically implement it too. I'm sure it'd be a change in the compiler, but it wouldn't be a change in the language.

> True. But beside the point.

Uh. No. There is a big difference between "every value can be nil" and "only some values can be nil."

And you don't need to sell me on the benefits of ADTs. All else being equal, I'd much rather have them. But this doesn't mean I want to go around shoving them into every language under the sun. I recognize that, sometimes, it's reasonable to persist without them. I very strongly believe that there is no One Right Language Design.

In Go's case, I've written a lot of it, and experience tells me that `nil` errors just aren't a large source of bugs like they are in a language like C. I suspect it is partially because you can dispatch on `nil` values[1], and also partially because of very very strong idioms like `if err != nil { ... }`. You can also `append` to `nil` slices.[2] I recognize that this is practical experience and that it will always lose against theoretical purity, but Go isn't after theoretical purity. (Please be careful. This is not a claim that the two things are mutually exclusive.)

> You would have the "grouping" behavior anyway, since in Go you don't declare that you satisfy interfaces. So if all possibilities for an ADT implement the same interface, then the ADT should magically implement it too.

You're not thinking through everything. What happens when the discriminants of a sum type are themselves interfaces? What happens when you type assert? Which value do you get?

What is the zero value of a sum type?

How are sum types deconstructed? (Pattern matching! But now you've added another language feature!)

Also, interfaces already provide some of the use cases of ADTs with type switching. You just don't get compile time safety. So now you have a case of non-orthogonal features.

Finally, you should note that I am not claiming "these things cannot be resolved." I am claiming that, "it is hard to resolve these things in obviously simple ways that are consistent with the rest of the language." By the time you're done resolving them, you will have made the language specification more complex.

[1] - http://play.golang.org/p/4_SNEi9YgR

[2] - http://play.golang.org/p/D3WRreGNBb