Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This is where `Option` or `Optional` types shine. They force you to unwrap the nilable value at compile time, and are zero-cost abstractions. It's not difficult to use option types, either--especially if the language features syntactic sugar to promote their ergonomics.

Rust, Swift, Guava, etc. all get this right. Option types need to become a language feature for all statically-typed languages. C++ should adopt it too.



> This is where `Option` or `Optional` types shine. They force you to unwrap the nilable value at compile time, and are zero-cost abstractions.

They aren't zero-cost abstractions in most languages. Every unwrap() potentially results in a dynamic check, even if one isn't necessary. There are a number of ways you could extend a type system to track the potential cases of sum types, which would help alleviate this cost.


I think in practice it isn't a higher cost: a lot of code in languages with nullable pointers also has a pile of unnecessary dynamic checks (e.g. checking arguments aren't null).


Exceptions (implemented zero-cost style) are truly zero cost. No dynamic checks as long as no exception is thrown.


Exceptions aren't zero-cost either: they require you to be able to unwind the stack, so you have to keep the frame pointer and have one fewer general-purpose register to work with.


You actually don't need the frame pointer to unwind the stack. DWARF frame info allows the compiler to specify tables that describe the stack layout at every program point, and this can be used to perform unwinding.

Another reason why exceptions aren't zero-cost in practice is that compilers need to model the exceptional control-flow, and they usualy don't do a great job in this situation and miss a lot of potential optimizations.


The cost being compared here is the 'if x.is_none()' or 'if x == null' check, not what happens after it.


Option types for references need be no more expensive than a null pointer check, and I believe this is how they are implemented in Rust. If a dynamic check is not necessary, then you simply do not use an Option type.


In C/C++, you quite often end up in situations when some other condition implies that a pointer is nonnull. In Rust, the equivalent code with Option would require an unnecessary dynamic check.


That's just a problem with ordering. If a condition implies a non null pointer then a non null pointer can imply the condition as well. This is true unless the condition being false does not guarantee a null pointer, in which case the condition is not useful. If some condition implies multiple non null pointers then the option type can be defined on a tuple of references


> in which case the condition is not useful

I contest. The reason for the condition need not be only to indicate whether the other value isn't null; if it was, you could just remove the condition and check nullity. In fact, unless there is either a case where the condition is true and the pointer is null or a case where the condition is false and the pointer is non-null, the condition is useless because checking the pointer would've been enough.

Consider the following silly example, where we have a function

    f(Conductor *c, bool isDriving, Train *t)
with the guarantees that c != NULL, and isDriving==true => t!=NULL. Note that the case isDriving==false does not imply t==NULL; indeed, c could just be waiting for the signal to start driving.


One can express the states more clearly with a slightly upgraded Option/Optional/Maybe, i.e. a full sum-type/enum. For instance, that function could be, in Rust syntax (but the same thing works in Haskell, OCaml, Swift, ...):

  enum ConductorTask {
    Nothing,
    Waiting(Train),
    Driving(Train)
  }
  fn f(c: Conductor, task: ConductorTask)
I think this says everything that your text says, but in a way the compiler understands and can assist the programmer with.

Alternatively, since isDriving only makes sense when there's a train, t could be Option<(bool, Train)>, i.e. an optional train along with a boolean for if it's being driven or not.


An Option type in Rust forces a particular memory layout by requiring the components to be contiguous, which might not be what you want if you are doing low-level optimizations of data layout.


That's true, and does mean one has to drop down to C/C++-style pointers and manual layout when the default enums don't work. However, that's somewhat orthogonal to the semantics about what they can express, especially for function arguments where the value isn't being stored in memory. One common approach is for an enum to be teased apart to be stored, and then rematerialised at the API surface (e.g. HashMap and it semantically storing an Option<(K, V)>).


So put a ref or a box in your optional.


I don't think adding indirection is what the parent was thinking of. It's more like storing the Option<T> Some vs. None bit in its own separate bitvector along with a packed vector of Ts. This layout, due to padding, may be significantly less memory than alternating bool, T like a vector of Option<T>s gives, but it still retains properties like contiguous layout and minimal allocation/indirection.


isDriving remains connected to t. Specifying an Option reference for t allows you to convey two pieces of information: if None, that isDriving is false and you do not care about t, and if Some, you have train t


Put the condition in the type. If you truly know that the pointer is nonnull, you can explain how you know to the compiler.


If, after inlining and so on, the compiler can prove that the condition implies the non-null/Some case, then the check can be optimised away.

If it can't, then the version without the check is potentially unsafe, and the check is not unnecessary.


While I love option types, retrofitting them into languages which already have nulls has problems.

Ask me about using a Java library that could return an option type, which could be null, so you have to check for both the None return value AS WELL AS null.


> Ask me about using a Java library that could return an option type, which could be null, so you have to check for both the None return value AS WELL AS null.

No, you don't -- because those are two different kinds of null!

If your library's function returns Optional.empty(), then it's because it successfully returned no value. If it returns null, though, then the library has a bug and you should crash instead of trying to continue in a known invalid state.

Without the Optional type signature, the function would return null for both of these, and your program will appear to work even though it's just suffered a bug.


> retrofitting them into languages which already have nulls has problems

I'm not sure I agree with that. See how MS did that in C# 7:

https://www.kenneth-truyers.net/2016/01/25/new-features-in-c...


FYI that didn't ship in C# 7

More info: https://github.com/dotnet/csharplang/issues/36


The situation is known and will be fixed when the JVM gets value types, with minimal value types already on the horizon.

https://wiki.openjdk.java.net/display/valhalla/Minimal+Value...

Until then it is an issue that we have to live with.


When or if? I heard one talk about Valhalla some time ago. If this project succeeds it will either be a miracle of engineering or a nightmare to use.

Just one example: Java's type erasure trick does not work anymore when value types are type parameters, so it affects the build process. The current approach seems to be to construct specific instances at run time (or rather at class loader time). That is very late though, because some information (like return types) are lost after compilation.


When.

The ongoing changes related to value types, AOT compilation, and overall mechanical sympathy are driven from pressure in the Fintech industry. Which has been moving into Java during the last decade, and currently is eyeing other stacks that could given them the benefits of Java alongside those features, like e.g. Pony.

So of course Oracle wants Java to stay relevant in those domains.

You are required to annotate type parameters for the old reference behavior, currently that would be with the any modifier.

Something like

    class Data<any T> {
    }


Retrofitting anything into anything always results in warts. Moral of the story: design things right, right from the beginning.


... which is practically impossible with larger projects that you can't oversee in a glance.


So don't bite more than you can chew?


That's a problem you already have in Java with any other type though. If you have a List or a String or whatever it could always be null.


> C++ should adopt it too.

They apparently got the memo: http://en.cppreference.com/w/cpp/utility/optional


For people using boost, this feature has been available for some time: http://www.boost.org/doc/libs/1_60_0/libs/optional/doc/html/...


std::optional is available in C++17. It has also been available in boost for a veeery long time.

Lack of good sugar for pattern matching sometimes makes its use a bit awkward though.


A new proposal is bubbling up.

https://www.youtube.com/watch?v=HaZ1UQXnuC8




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: