3 ms·
It's a really common pattern that you want to write Object value = a.b.c.d; for something like a JSON value you got over the web where any of it might be n
by PaulHoule 10mo ago
It's a really common pattern that you want to write
Object value = a.b.c.d;
for something like a JSON value you got over the web where any of it might be null but have to write something like
Object value = nonNull(a) ? nonNull(a.b) ? nonNull(a.b.c) ? d : null : null : null
or
Object value = null
try {
value = a.b.c.d
} catch(NullPointerException x) {}
Just being able to write something like
const value = a?.b?.c?.d;
is a great relief. If it is just value lookup letting a.b return null if a is null would be fine with me but there is something a little creepy about a.doSomething() doing nothing and returning null in that case.
Personally I am not a fan of Optional<T> and (worse) Either<A,B> in Java for various reasons. I think the official functional stuff in Java (like the streams API) is awful, but I like working with functions like
Object value = nullSafe(a,x=>x.b,x=>x.c,x=>x.d)
The author has some affinity towards a collection-first or collection-only style of programming and certainly I have written programs or subprograms working with dynamic structures that leaned heavily into List<T>, that is, a value which might be present or absent can be treated as a List which just happens to have 0..1 members and x.map(y=>...) and other functional operators do the right thing for the 0..1 and 0..N cases and if you use the same set of operators for both of those cases they just roll off your fingers and you are less likely to forget to put in null checks and when you compose more complex operators out of simpler ones it tends to "just work"
- tekne 10mo agoMy personal preference would be to have separate types `A` and `A?` and overload field projections to work on `A?` as you'd expect. Subtyping optional. Putting on my theory hat, while we we can overload field projection for any monad (data structure representing a result + an effect : think result for exceptions, or a thunk for a promise), it's not the best idea. But for what is at least morally a commutative (order doesn't matter: FAIL ; pure = pure ; FAIL = FAIL) and idempotent (FAIL ; FAIL = FAIL) monad, it works... Which justifies fun things, like lazy projections on promises!
- PaulHoule 10mo ago... or maybe in the definition of type A you can specify what ((A) null).someValue or ((A) null).doSomething() does at the field or method level. I guess there is the issue of how you figure out what the type is which depends of course on the language. Could be a better fit for C++ than all-virtual Java but I don't want to give the C++ any ideas to make that language more complex!
- tekne 10mo agoYou could, but the issue is that now when I'm defining a type I need to think about it's nullable behaviour. Versus if I instead view nullability as a way of transforming types (aka a functor) that works by reflection; this gives me some parametricity: the behaviour of my function will be the "same" regardless of which generic I slot in (though fields don't play that well with generics... something something row polymorphism something something). Ill formed thoughts really; what I'm handwaving at is I slightly agree with the anti-complexity crowd that an operator should usually do the same thing, and yet I think it harms terseness and hence often readability and understanding to have non-overloaded boilerplate everywhere (addf64, addu32, addi16...). Parametricity is a nice compromise for non-primitive ops, imo.
- PaulHoule 10mo agoYeah, there are two ideals I think of in terms of programming languages: (1) As a simple set of primitive operations that I can use to build up increasingly complex operations and on that level I value "simple". Like the all-virtual method dispatch in Java as compared to whatever it is in C# or C++. In that case I value predictability and users knowing what they're going to get. (2) As a platform for domain-specific languages such as the tactics described in On Lisp although those tactics apply well to other languages. In that case my desires as an "author of libraries" and "users of libraries" start to diverge. With that hat on I start to think (a) application programs can consist of "islands" with radically different coding styles (say in Java part of the system is comfortable with POJOs but other parts work with dynamically structured data with no schema or a schema defined at runtime) and (b) I'd like to be able to bend the language really far. From the viewpoint of a programmer who wants to get work and have an impact on the industry I'm usually working in commercially mature languages and finding as much of those values in them as I can.
- OptionOfT 10mo agoMinor correction: Object value = null try { value = a.b.c.d } catch(NullPointerException x) {} and const value = a?.b?.c?.d; are not the same. The ?. operator returns `undefined`, even if the operand is `null`. Also, Optional<T> in Java is kinda weird. It's a mechanism to enforce null checks. I much prefer pattern matching like in F# and Rust, where you get an Option<T>: https://fsharpforfunandprofit.com/posts/the-option-type/ https://fsharpforfunandprofit.com/posts/the-option-type/ The ergonomics of matching and only having the variable when it's actually Some(_) makes it very natural, so much so that switching languages feels painful, as I'm constantly doubting myself. I don't need to read documentation about whether the output CAN be null/None. If it can be, it is encoded in the type system. This is unlike Java. Many types can still just return null.