Java API design (for Kotlin consumption) ... Optional or null?

Thanks for the feedback and taking your time to challenge my point of view :slight_smile:

In principle all of the features could be implemented, however, at the currently state more or less every derived method from the basic monad methods is to my knowledge not directly present. As far as I can see, I’d need to do some rewiring to call foo.map(something) on a T? - correct me if I’m wrong :slight_smile:

[quote=“Wasabi375, post:19, topic:2455”]Ok you don’t like the syntax, that is however no argument against nullable types in kotlin. Also the kotlin syntax is quite consistent. Every time you see a ? you deal with nullable types.
[/quote]

list.map(something)
stream.map(something)

is not consistent with:

if (nullable != null) { something(nullable) }

I could of course make something take a nullable, but then I’d still end up with:

something(nullable).?....

which is still inconsistent. Similar for flatMap. So yes Kotlin is in general consistent, however, this specific design is not consistent with the normal method calls of Monads.

Hmm, I’d have though people got used to it by now, after all we had in since Java 8, C# had it for even longer, and similar is also present in JavaScript. That said, if you are right, I’m inclined to argue that this stresses the importance if not introducing extra notations for the same thing. If things are not well understood, having multiple notations are not going to help.

[quote=“Wasabi375, post:19, topic:2455”]How so? Kotlin has checks at the beginning of all public functions to ensure nullability is not broken by java and in the java world you have to check for null in any case. The only thing to ask for is to return Optional for those java libs/programs that use them but that is not always the case.
[/quote]
Breaks might be the wrong word. But the second Java code is involved then returning a null introduces the same hell that we’ve been dealing with since the invention of the null, hence the opposition towards nulls and the experience that nulls are bad.

That there is nothing in the Option. Unless misused the meaning should be fairly clear. The meaning of null is not clear, in some frameworks it means that a query to a database returned no entries (should have been empty list perhaps), in some it’s an error case, in some it leaked from somewhere and the meaning is garbled except it’s a clear error. Also one could argue that Option is a special case list with 1 or 0 elements, where the 0 element list is denoted by a separate value, still a clear definition to me at least.

Making the specific meaning even more clear. Required? No. But replacing booleans with meaningful enums are not required either, however it is often a good design. E.g., printJson(true) vs printJson(JsonFormat.PRETTY) should make it quite clear what the value of making ones names more explicit in the intent.

Running out of time, but will take a look :slight_smile: Seems interesting

Switching language is not an option in my case, and not my purpose. I’m not saying Kotlin is bad, in fact I love it. What I’m trying to challenge is the idea that because we got nullable types means that we should throw out all of our experience gained through the last 40 years or so. Even though we encapsulate null and deal with the worst of it, we are still not safe. It is entirely true we aren’t either even if we follow the Optional pattern and wrap our nulls in classes (e.g., serialization frameworks can still ruin this). I’d still argue that we make it much harder to accidentally introduce an error, if we try to avoid nulls. This standpoint might be up for review, for instance pending on the nice links provided.

Seems to be the point of one of the links provided by @arocnies. Can’t read it right now, but looking forward to it :slight_smile:

What I’m trying to challenge is the idea that because we got nullable types means that we should throw out all of our experience gained through the last 40 years or so.

What experience?

I see only three main problems with null, as used in Java:

  1. As you mention, it’s used to mean different things (unspecified, absent, uninitialised, error, &c), with no syntactic way to specify which. (That should always be spelled out in the docs, of course!)

But I can’t see how any of the usual alternatives (e.g. Option.None) can avoid suffering from this, too.

  1. There’s no syntactic way to specify whether null is possible at any point (whether a method will accept it as a parameter, whether a method could return it, &c). (Again, the traditional workaround in Java has been to specify this in the docs. Now we have annotations, but they’re not very standardised, and quite verbose.)

Kotlin fixes this! By building null deep into the type system, you always know whether it’s a possibility. And it’s only a single character.

  1. There’s nothing to stop you using null where it’s not expected, or trying to dereference it.

Kotlin fixes this too! You get the full power of static type-checking preventing you from passing null where you shouldn’t, and from dereferencing anything that could be null.

Yes, Kotlin’s null handling is a special case, building into the language something that could potentially be handled in a library. But it’s such an important case, so widely used, so easy to abuse, and sufficiently different both in use and in intent from the other common monads, that I think it makes good sense to treat it specially. And building it deep into the type system works in a surprisingly elegant way.

Finally, it’s so much less verbose than something like Optional, as well as more efficient!

As for Java interop, I’d suggest that Optional is sufficiently new and patchily-used that you’d probably be justified in omitting it so as not to pollute the Kotlin side. And Java can use the nullability annotations that Kotlin emits, so you still get some benefit.

Agreed it’s not if you compare List.map and nullable syntax. The reason for that is that kotlin IMO tries to move away from the theoretical concept of a Monad for nullable types, since they are a bit harder to understand when first learning about them. Still if you really prefer the Monad system you can simply implement those functions for yourself as an extension

inline fun <T, R> T?.map(block: (T) -> R): R? = this?.let { block(it) }

I hope I didn’t mess this up :wink:

I think most people understand map, flatMap, etc when applied to collections but they are a bit harder to understand when applied to Optionals. I personally have no idea how widely used Optionals are in Java or C# but I can’t say I used them in either language (3 years of using java professionally at one company and hobby game dev with Unity and C# for I don’t know how long).

Yes, but those errors will always be part of the Java code and can’t be handled by kotlin.
Also the invention of nulls makes it sound like there ever was or will be a time without it. Optional.None and null are the same, dressed in different ways. Optionals come with enforced null checks every time you use them but so do kotlin nullables. They look different but in the end they are just manifestations of the idea that a value can be unknown or not yet calculated.

I think your original example was typealias EmptySearch = Option.None. If it’s just about being explicit when calling a function you can just use const val EmptySearch = null.

I agree. Most programs become easier when we try to remove all instances of values being unknown, but that also goes for removing Optionals. I don’t think you gain anything (in kotlin) by replacing Nullables with Optionals.


I will only address this point since I don’t think this is a direct problem of either null or Optional rather in the way we used to (or still) program. I might be wrong but I would say that this falls kind of into the same problem as magic constants. You arbitrarily define some meaning for a constant and hope everyone in the program keeps to it. With null it’s even worse because we change the meaning of the constant every function.
It’s not the problem of null directly but a problem in how it’s used. Kotlin has a nice solution for this with sealed classes. If you have a complex function with multiple different results and maybe errors just define a sealed class to encapsulate all the different results.