Why does subtype checks use `<:` instead of just `<`

I was wondering why for checking subtype (I am not even sure that’s the right terminology) the operator is <:. E.g Int <: Integer returns true. But wouldn’t < work as well syntacially? E.g. Int < Integer.

So my question is: why the syntactic choice of <: instead of <? I am sure there’s a good reason, I just can’t think of it.

For one thing, the meaning of < is very different from the meaning of <:

julia> 1 < 1
false

julia> Int <: Int
true

Additionally, this would just be confusing, since if you assign Types to two different variables and then compare them with < further down in your code (or even in another function in another file), anyone reading your code might reasonably assume that the two variables are number types that can be compared like that, when that is not actually the case.

3 Likes

So maybe Int < Int should return false and Int <= Int should return true. Doesn’t this give finer control? So not 100% convinced by your reasons yet.

If that’s what you want, then I can see the addition of an operator like <<: or something like that. But that’s still not a reason to re-use a mathematical operator for doing type comparisons.

I mean, you don’t have to be convinced by my reasoning, but if you wanted to see <: changed to <, then what reasons do you have for wanting this? I don’t personally see any clear gains or increase in code clarity from this change.

1 Like

Also, in additional to the absence of reason to reuse an operator, types are not ordered. A not being a subtype of B does not imply B is a subtype of A.

5 Likes

* is reused for string concatenation although I know that Jeff wasn’t a fan. So there is precedence. Again, not convinced.

It is ok to “not be convinced” when others have conveyed the rationale. Not everyone agrees with every detail of Julia’s syntax. The user community does agree that Julia is easily expressed and it works well. The way that Julia is expressed has been determined through years of discussion among people whom I, for one, respect.

1 Like

For the avoidance of doubt, i have the utmost respect for the developers of the language and every poster here. It seems that you take “not convinced” to mean at some level disrespect. If that language caused such connotation, I am sorry. What i meant was that “I am not convinced that was the reason <: was chosen”.

I think there might be another reason or reasons. Obviously, it is already chosen and as i mentioned in the orig post that i think there is a good reason. I just don’t know what it is and i am not convinced that the reasons being put forward were the main reasons.

The reason that < is not used with Types is that Types do not relate in the way that e.g. Integers, Rationals and/or Floating Point values do.

Two distinct sorts of relationship → two distinct symbolisms.

To conflate the symbols used would be to muddy their proper use.

also consider f(::Type{T}) where {T<:Real} note :: and <:

1 Like

That is plausible.

I wonder if one the language designers can chime in and put this to rest. They must know the exact considerations.

Quoting @StefanKarpinski from here:

String concatenation is the algebraic operation for the monoid of strings (under concatenation) and is commonly written mathematically as juxtaposition or multiplication

so string concatenation is a mathematical operation, and hence the use of * for it is justifiable.

1 Like

I’ve already given you a reason.

2 Likes

<: and < have some fundamental differences for one <: is a built-in function while < is a generic function.

julia> <:
<: (built-in function)

julia> <
< (generic function with 70 methods)

Being a built-in function you cannot create new methods or override existing methods of <:. It is far more fundamental to Julia than generic functions. < on the other hand is a generic function which can be defined for new types. If you used < instead of <: I suspect that it would no longer be feasible for < to be a generic function.

1 Like

This isn’t a problem. A method of < can be implemented just fine using something else as the real function (e.g. issubtype). (I.e. there’s no technical reason <(a::Type, b::Type) = issubtype(a, b) can’t be added)

1 Like

That function cannot be used in the definition of functions

julia> <(a::Type, b::Type) = issubtype(a, b)
< (generic function with 1 method)

julia> f(A::T) where T < Number = 4
ERROR: syntax: invalid variable expression in "where"

And that’s only because the parser doesn’t like it. It has nothing to do with the actual function (i.e. you were never using the <: function in function signatures either). Even if you make const < = <: it still won’t work even though by all mean now in your scope < is a builtin function.

There’s no fundamental issue with changing the parser to accept < and not <: there in the syntax. It doesn’t even have to be consistent with the function< (or <:) though being consistent here is user friendly for sure.

In another word, you are using an implementation detail to explain a design choice. It’s not necessarily wrong in general but in this case the implementation of the function is completely independent of the syntax and the use of builtin function is also fairly irrelevant.

1 Like

if <: were a generic function then it would be possible for it to have specialized methods. For example wouldn’t there be issues with the possibility of defining a function such as <:(a::Type{Int},b::Type) = true invalidating large portions of code, and wouldn’t this undesirable behavior for something that is so fundamental to the language?

Well, we are never in the business of preventing you from shooting yourself. I don’t think that’s much more or less fundamental than + and we allow overwriting base implementation that crashes the REPL (NOT the runtime) easily. This is especially true now that the compiler, which is the heaviest user of type checks, won’t even care about it.

It’s not the nicest thing in the world for sure. It’s also not nearly the worst thing of similar nature we have.

In any case, I wasn’t arguing about this part anyway. I’m only saying that if your original claim that,

is ture then it’ll certainly be a good reason. It’s not. And the fact that users can trick themselves by overloading things they shouldn’t do isn’t as important a reason anymore.

We didn’t make <: up, it’s the standard syntax for the subtype relation. The < operator is typically reserved for something that is at least nearly a total ordering, which the subtype relation is not.

7 Likes

And although this is certainly OT, being able to override subtype behavior isn’t unconditionally a bad thing. Just like overriding getproperty, there are people that want it and like many thing else, python has it. I’m not saying julia should or should not, but just an example of the balance between being customizable and being able to shoot youself…