Im in my 40s now and have seen many hyped language features come and go and very few that I think haven proven to be a net good thing that truly make programming better. Option types are on of the few. I think every language should have them now. It is more convenient, safer, and modern development machines can often compile away the tiny overhead.
Explicitly being able to mark a type as nullable and forcing null handlong if so. See https://kotlinlang.org/docs/null-safety.html it works pretty well with java interop by respecting jsr annotations and auto checking for null
I think for most intents and purposes, option types and Kotlin’s approach are more or less equivalent. Either way, the compiler helps you avoid null dereference errors.
I like Swift’s approach personally, which is that there’s an Option type, but loads of syntax sugar to make working with it easy (just append a “?” after the type to flag it as optional, “if let”, optional chaining with “?.”, “?()”, etc etc.)
For Go's purposes, though, they aren't equivalent at all.
Option types require generics, which Go doesn't (currently) have and may or may not be enthusiastically embraced by the community.
Option types must also be baked into the language, standard library and culture from the beginning in order to be ergonomic; otherwise you're constantly having to wrap and unwrap them whenever you interact with code that was written before the type was introduced. Which is most the code you interact with.
Finally, my own experience has been that Option types generally make things more rather than less complicated when you introduce them to a language that already has null. You'll need to make a decision on whether Some(null) is an allowed value, both options will introduce language warts.
Kotlin's approach, on the other hand, does not require generics to work, and interacts very well with the JVM's existing libraries and its existing type system. It's not going to butter everyone's toast. You can't do anything monadic with Kotlin's approach, for example, but I don't get the impression that the Go community is just desperate for monads.
I suppose it wouldn't. But, if you're going to do it as a feature that's built into the core language, I'd say that's even more reason to just do it Kotlin-style. The other main downside of doing it that way is that it needs to be baked into the core language and can't be pushed out to the standard library the way you can with optional types. But that wouldn't really be a practical difference compared to an optional type implemented without full-fledged generics.
The other big distinction between the Kotlin approach and optional types is that the Kotlin approach introduces no new run-time types. Null safety is checked statically, and then you're done. That's a big part of why it plays nicer with an existing standard library. It also means, though, that you introduce no extra run-time overhead, which I would assume is something that's considered pretty desirable to gophers.
They're not quite the same. With option types `Some(x)` is a distinct type and value to `x`. That's not true in e.g. Kotlin or Dart. For example this is fine in Dart:
void foo(int? x) {}
void main() {
foo(5);
}
This is not fine in Rust:
fn foo(x: Option<i32>) {}
fn main() {
foo(5);
}
You might not think that makes much difference, but consider if `foo()` starts off as `fn foo(x: i32)` and after using it 10k different places you want to change it to `fn foo(x: Option<i32>)`. That's a backwards compatible change in Dart (and I presume Kotlin), but not in Rust.
You can do this with typescript. If strict mode is on, any type that can be undefined or null will cause a compiler error if the condition is not explicitly checked for.
* val input: String is a non nullable String. You can never assign null to it, and null will never be assigned to it (unless you do some reflection stupidity, or really, really rare cases of you're-fucked-anyways-if-that-happens-something-is-really-wrong)
* val input: String? is a nullable String. It's basically String|null.
There's a third option, which is String!, platform types: since Kotlin has interop with Java, Kotlin will err on trying to be practical for you when calling Java code: it will (rightly so) assume that it is a non-nullable String, but still warn you that it doesn't have the necessary info to infer nullability/non-nullability, so it could technically be null. It's up to you to decide if you want to null-check it. This can be solved in two ways:
- Update your java code to include @Nullable/@NonNull annotations. You should already be doing that anyways, especially if you have control over it.
- Don't call Java code/wrap it in Kotlin wrappers. Kotlin code does not have this type inference issue.
I don't know Kotlin, but these usually are distinct from Options in an important way, idempotency `String?? = String?` or `String|null|null = String|null`.
This equation makes sense for some mental models, but isn't quite the same as Option. In particular, Option[T] has the nice property that you can map over its inner type in a naive fashion `forall S. (T -> S) -> (Option[T] -> Option[S])` whereas the coalescing version above needs an additional assumption that `S` is non-nullable.
forall S not null. (T -> S) -> (T? -> S?)
This can really get in the way of some kinds of generic programming.
Not necessarily. Not only is it Option.Some/None/ProbablySomeBuyMaybeNoneBecauseJava, it doesn't offer the exact separation that Options can offer: you can stick a value in Option.None (String? can contain null or ""). Nullable types are different to options.
(Bonus point for Java that has introduced Option types, that can still be null)
The problem with Kotlin is that it only has one global "anything-but-null", so nested ?s are collapsed (String?? is equivalent to String?). This is because it needs to run on the JVM, which only knows about is-null and is-not-null.
This interacts terribly with generics, since it means that only one side of the generic boundary can "own" the null value at any one time. To simplify a case where this can cause issues:
class Loadable<T>(
// null until value is loaded
var valueIfLoaded: T?,
)
// returns null if user is not found
fun getUser(id: String): Loadable<User?>
Code that interprets Loadable<T> will get stuck showing a loading bar, since it has no idea about getUser using null for anything else. This doesn't even generate a warning!
The "correct thing to do here would be to add a `T: Any` bound on Loadable, which prevents T from itself being a nullable type. That would notify the author of getUser that they need to box the User?, so that the cases are kept separate:
data class Box<T>(val value: T)
class Loadable<T>(
// null until value is loaded
var valueIfLoaded: T?,
)
// returns null if user is not found
fun getUser(id: String): Loadable<Box<T?>>
These are things you don't even have to consider when using a typical well-designed Option type (which, mind you, could still have a syntax sugar for T? if you wanted it).
I'd argue that they are fundamentally different. Option is "just another type". You interact with it using constructors, map/flatMap/get etc. You could implement Optional in Java 1.5 but can't implement nullable types in current Java.
Nullable types are a feature of the type system and require language support, but the benefit is that you don't touch typical programming patterns
val foo: T? = x()
if(foo == null) { return ... }
// foo is now T, optionality gone
v.s.
val foo: Option<T> = x()
// following must be wrapped and sprinkled in .map, .flatMap, .orElse
You can sort-of achieve the first with .isPresent() + .get(), but it clutters the scope, is more verbose, and is not really idiomatic.
Really? I haven't used Java in a long time, but in the languages where I've used Optional types, you'd always check for presence once and then extract the value before further use.