Explicit nulledness vs. requiring proof-of-property/flow typing

Look at JEP 401: Value Classes and Objects (Preview)
If it is not interesting for you we certainly are in different domains.

You have suggested to use the wrappers. Actually if it were necessary I would use java collections internally in the api. I don’t like the idea just double up the amount of long lived objects in the heap.

It is interesting, though I’m going to need a bit more context to understand the relevance to the current discussion.

I don’t quite understand, do you mean in the previous example the None|Some(null)|Some(_) was part of the internal implementation?

If so, a Unknown|Unset|Cached(_) ADT could reduce the number of allocations, as you’d be using a single Unset object instead of multiple Some(null) instances.

If not it’s not part of the internal implementation, then I’m having trouble understanding the scenario you’re describing, as wrappers around the return values should be fairly short-lived.

1 Like

Ok, it seems that I think in scala2. )))
IIUC: You have suggested to use union types.
Currently I don’t completely understand how it works, if it is really so it is interesting.
Scala2 does not have union types so what do you suggest for Scala 2, or should someMap.get work differently?

IMHO: It is not bad that ‘map.get’ can return ‘some(null)’

I’m still in Scala 2, I’m just using a shorthand for a hypothetical CacheState[A] sealed trait:

  • object Unknown extends CacheState[Nothing]
  • object Unset extends CacheState[Nothing]
  • final case class Cached[A](value: A) extends CacheState[A].

True, however that’s not really what I’m saying. Yes, it’s handy to be able to store a null in a Map in specific, well-profiled, and isolated situations, it’s just what you’d expect to be the go-to “normal” approach.

Stated another way: it’s a little evil (because it subverts expectations), but it can be less evil than bottlenecking the app :wink:

Getting back to the original topic…

How is “a value must fulfill the property of not being null” not exactly what Scala 3 Explicit Nulls is? “A variable cannot hold null” might be a description of some @NotNull annotation. But in Scala 3 it’s part of the type system, and that means it applies to values, not references.

In fact, the Explicit Nulls feature includes a limited form of flow typing. For instance if I’m not mistaken in simple cases the type is affected by if tests.

Getting back to the original topic…

Thank you :blush:

How is “a value must fulfill the property of not being null” not exactly what Scala 3 Explicit Nulls is?

In fact, the Explicit Nulls feature includes a limited form of flow typing. For instance if I’m not mistaken in simple cases the type is affected by if tests.

Limited being the keyword. The idea of these “type proofs” is to prove arbitrary properties about a value in an additive fashion; a test of if x != null && x > 0 would prove that x is in fact not null but also that it’s positive. In contrast, Explicit Nulls works in a subtractive way by removing the null type from the equation.

In the “proof scheme” a type definition of : AnyRef is nullable. There is no compiler flag that changes this from nullable to non-nullable. Even so, it is possible to declare a value that can’t hold null on the spot by requiring a proof of this and providing one:

// Explicit Nulls
val x: String = if y == null then "not available" else y // where y: String|Null`
// type proofs
val x: Proof[String, NotNull] = if y == null then "not available" else y // where y: String

Proving facts about a value can be made more palatable by the addition of syntactic sugar. Additionally, expressions such as val x = "sassafras" can’t be null and so can be inferred as Proof[String, NotNull & Singleton["sassafras"] & …], where the part intends other properties that can be deduced.

It is in fact a debate that apparently has been had at least once, in scala/scala#7336

1 Like
val obj1 = Json.parse("""{ "key": null }""")
val obj2 = Json.parse("{}")
assert(obj1.get("key") == Some(null))
assert(obj2.get("key") == None)
3 Likes

In the case where a option type needs to be able to carry Some(null) then that would probably be more clearly encoded in a different type (e.g. NullableOption).

I’m also not convinced that a normal Option class needs to be able to encode Option[Option[X]], e.g., Some(None). In most Option usage I have seen, one level of Option is sufficient, and using a separate type when it needs to be more flexible makes sense to me.

I.e., I would be much happier if the mainline Option class in Scala could generally be encoded without any additional memory or processing overhead.

When you use a Map[X, Option[Y]], you are indirectly creating Option[Option[Y]]. And it is critical for correctness that Some(None) be distinguishable from None.

5 Likes

That example breaks down a bit when you start assigning types. For example:

val obj1: T0 = Json.parse("""{ "key": null }""")
val obj2: T0 = Json.parse("{}")
val obj3: T0 = Json.parse("""{ "key": 1 }"""
val r1: Option[T1] = obj1.get("key")
val r2: Option[T2] = obj2.get("key")
val r3: Option[T3] = obj3.get("key")
assert(r1 == Some(null))
assert(r2 == None)
assert(r3 == Some(???))

Unless you’ve got some impressive type-level shenanigans going on, T1, T2, and T3 have to be the same type, and unless that’s Any (granted, a popular choice for Java libraries), it’s generally an AST.

Knowing that, it’s unsurprising that most Scala JSON libraries define an object in their AST for explicitly null keys (e.g JsNull, JNull, etc).

get returns Option[JsValue] in this example. This hypothetical API does not (to my knowledge) correspond exactly to an existing one, but there are quite similar JSON APIs out there