Hacker News new | ask | show | jobs
by zarakshR 452 days ago
> The language can just have an implicit conversion rule for convenience

the presence of an implicit conversion rule `T -> T?` amounts to the observation that `T <: T?`, where <: is the subtyping relation

> make an unboxed integer nullable ...

I don't think any language allows this, in any case disallowing nullability for unboxed types amounts to the observation that `P !<: P?`, where !<: is "does not subtype"

I believe (unless I have misunderstood you) that both your examples are subsumed (heh) by subtyping

2 comments

> the presence of an implicit conversion rule `T -> T?` amounts to the observation that `T <: T?`, where <: is the subtyping relation

Not necessarily, because you might consider it acceptable for the implicit conversion to change the memory layout in this sense.

> I don't think any languages allow this

Plenty do, e.g. Rust's Option works that way.

> in any case disallowing nullability for unboxed types amounts to the observation that `P !<: P?`, where !<: is "does not subtype"

Saying the same thing with fancier words doesn't explain anything. The point is you can't simply treat non-nullable as a subtype of nullable in general, because this case exists.

> Not necessarily, because you might consider it acceptable for the implicit conversion to change the memory layout in this sense.

Does the actual data layout impact the observation?

If you have A, something that accepts B, and you consider it implicitly possible for an A to be a B with either no change or an implicit change... that seems to amount to considering As to be Bs when necessary.

> If you have A, something that accepts B, and you consider it implicitly possible for an A to be a B with either no change or an implicit change... that seems to amount to considering As to be Bs when necessary.

The fact that an A can be implicitly converted to a B in this context does not mean that an A should always be implicitly converted to B. (In this case B is effectively a pointer/reference to A; implicitly forming that reference is a useful convenience feature in some contexts, but I don't think treating A as a subtype of reference to A in the general case is a good idea)

But isn't it being implicit what makes it a subtype? It doesn't have to be a pointer; integer coercion should also be considered a form of subtyping.

I don't know what to make of context here; if you're referring to whether you have a pointer to a type as context, I think it makes more sense to consider that part of the type (i.e. a pointer a A is a subtype of a pointer to B, while A is not a subtype of B)

My point is that allowing an A to implicitly convert to a pointer to A when calling a function that takes a pointer to A is reasonable, whereas always allowing that implicit conversion anywhere in your program is rather less so. The same applies to integer conversion; there are contexts in which it should maybe happen implicitly, but not everywhere.
> The point is you can't simply treat non-nullable as a subtype of nullable in general, because this case exists.

But surely, you can still use subtyping in other cases -- when it is already unboxed -- right?

Like so: `T <: T?` for all boxed `T`.

> But surely, you can still use subtyping in other cases -- when it is already unboxed -- right?

Well, maybe. But you have to actually have to do the legwork of figuring out which cases are subtypes and which aren't - unboxed was the first case that comes to mind, there might be others. Restating the same thing using symbols doesn't help make anything clearer, it just gets in the way.

> the presence of an implicit conversion rule `T -> T?` amounts to the observation that `T <: T?`, where <: is the subtyping relation

So you assert that numbers and strings are the same type because PHP and Javascript will implicitly convert back and forth? That any C++ implicit constructor creates a subtyping relationship?

> I don't think any language allows this

Rust, Swift, Zig, just off the top of my head. C# too, value types is where and why it first introduced explicit nullability (back in C# 2.0).

> because PHP and Javascript will implicitly convert back and forth

If you can provide (valid!) methods `T -> U` and `U -> T` for two types, why wouldn't `T = U` hold? (Atleast for types where `=` makes sense)

This is the definition I am using:

> S is a subtype of T, written S <: T, if a value of type S can safely be used in any context where a value of type T is expected.

from Pierce's "Software Foundations"

Of course, you may not want to make `String <: Number` and `Number <: String` (and thus `String = Number`), because there is no sensible way to do so; but this is an issue with the way PHP/JS handles subtyping, not with the notion of subtyping itself and certainly does not apply to the example of nullable types.

Unfortunately, I am not familiar enough with C++ to comment on the other question, sorry!

Yes, numbers and strings are the same type in Javascript and PHP. And yes, a C++ constructor declares a subtype relation.

That's the semantics of those languages.

Yeah I think this is "Deref coercion" in Rust.[0]

For example since Vec can Deref to Slice, you can syntactically run Slice methods on a Vec; meaning Vec is a subtype of Slice.

[0] https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coe...

As I revisit, this also comes up more explicitly when dealing with lifetimes as a type, and stacked borrows.

[0] https://doc.rust-lang.org/nomicon/subtyping.html

Lifetimes are the only subtyping relationship in Rust, yes.