Should Optionals be used by WPILib?

There’s been consistent feedback about how some WPILib DriverStation methods return default data before a connection and how that’s caused issues in teams code. For example: getAlliance() always returning 'Red'

Java 8 and C++17 introduced Optionals which provide programmatic ways to indicate that data hasn’t been received yet. This would be the “modern” way to update the API. However, there are other ways (such as adding an unknown to the enum).

There’s been consistent feedback that functional programming and lambdas used in command framework are hard to explain.

How do you think Optionals should fit into the solution?

  • I use optionals, they should be in WPILib
  • I don’t use optionals, they look easy, they should be in WPILib
  • I don’t use optionals, they look hard, do something else

0 voters

Additionally, should only the worst offender (getAlliance) be updated, or everything in DriverStation that has the same problem.

  • Only update getAlliance()
  • Update all relevant methods in DriverStation
  • Don’t break the API

0 voters

I’ve heard optionals come with (somewhat significant?) extra runtime cost, is this true? I know we already kind of ignore this by choosing to allocate objects constantly in our loops, so guess this won’t be a factor in the decision either.

Otherwise love the expressiveness of optionals.

2 Likes

I voted “use optionals” but an alternate enum state is totally fine too. I just think this should be fixed.

18 Likes

Overhead of Returning Optional Values in Java and Rust | Piotr Kołaczkowski (pkolaczk.github.io)
Yes.

4 Likes

I believe this video well explains why it should be an optional. If you add an unknown value, the people who don’t read the docs won’t notice it. Whereas if you use an optional it becomes very obvious that it can just… not exist. Compiler time errors are always better than runtime errors, especially when said runtime errors can cost you a $500 match simply because the error is impossible to run into in practice.

5 Likes

There aren’t many places in WPILib with nullable return values; should Optionals be used for all of them?

I mean, the alliance enum already has a (currently unused) INVALID option: allwpilib/wpilibj/src/main/java/edu/wpi/first/wpilibj/DriverStation.java at 663703d3708eba67ab42040b7d29a2c1099d0d9e · wpilibsuite/allwpilib · GitHub

As for the poll… I think I’m okay with either approach in this instance, so whatever makes sense for everything else. Then just be consistent.

1 Like

I kinda wish javac did an optimization pass to catch basic stuff like this. The JVM clearly isn’t smart enough to handle it.

I am not a Java programmer, so I have little exposure to Optionals. I did see them for some of the vision code (PhotonVision) and they seemed fine.

However, it does still seem unnecessarily more complicated than adding a “invalid” value to the enum. This has been available since enums were “invented”, and for me, has always been part of my work (ie most enums have a “n/a”, “undefined”, or similar value).

So, my vote would be to extend the enums to have the appropriate “error”/“not valid”/“unknown” values, as needed. Use Optional for non-enums (or just use Null).

What Optional is for

Optional<T> is actually a functional programming language construct used for error handling (though not exclusively). It’s the sum type between null and T (i.e., it can either have a value or not). Result<T, E> is another common FP error handling type that I don’t think Java has.

std::option - Rust (C++ calls it std::optional)
std::result - Rust (C++ calls it std::expected)

It’s not more complicated, just a different style you aren’t used to. You’d still writing a similar amount of code, but the Optional vocabulary type communicates intent more directly by separating nominal cases from the exceptional case at the type level. “Invalid” isn’t another alliance kind; it’s the absence of an alliance kind.

Why Java’s Optional is bad

With that said, Java’s implementation of Optional is poor. Its performance is much worse than C++ or Rust due to heap traffic, and it not being a proper monad means you can’t do Java 20 pattern matching on it. At least they have member functions like ifPresent() and or().

Here’s more context on optional:

We’ve been seeing garbage collection (GC) pressure from lots of small “value types” like geometry and matrix math, and Optional adds to that. Even for built-in types like Optional, Java just isn’t designed for what we’re trying to make it do (GC is why we can’t have nice things).

Why it probably won’t get better

A modern, low-pause GC could alleviate the GC pressure, but none of them are available for 32-bit ARM (the roboRIO). Control system refreshes generally last for at least 5 years, so the earliest we could change the target platform is 2027.

The Project Valhalla proposal’s value types may help avoid allocations, but past instances of the “we have X at home” meme in Java make me pessimistic. There’s:

  • Generics
    • Not reified, so can’t be used for function overloading
    • It’s a glorified cast to/from Object
  • Optional
    • Poor performance
    • Missing functional features
  • Destructors
    • finalize() was a failure that got removed from the language
    • Cleaner is just deferred destruction with more manual control over when the destruction occurs.
    • try-with-resources and AutoCloseable are a super verbose workaround for lack of automatic destructors, and escape analysis isn’t perfect
1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.