Julia: Consider a separate reserved word for `where` for T without bounds.

Created on 11 Jul 2017  Β·  18Comments  Β·  Source: JuliaLang/julia

I'm filing this Issue to follow-up on a conversation @JeffBezanson and I had after his JuliaCon2017 talk The State of the Type System (sorry it took me so long!):

We should consider using a separate keyword for the where T syntax in the new type system for the cases when there are no variable bounds. For a new user (at least for me and @denizyuret), the current syntax is confusing: it doesn't read as proper english. I keep thinking, "where T what?"

function f(x::Array{T, N}) where N where T
end

I can't help but read that and ask, "where T what?" Even worse, since I expect something to come after it, I find myself parsing the next bit as if where N where T is all one phrase -- as if where T is gonna be the bounds for where N.

This syntax only clicked for me once Jeff got to the later slides. I now understand that the word choice for where comes from the possibility of bounding the type variable:

function f(x::Array{T, N}) where N where T<:Real
end

Now it makes sense, but before that I was very confused. So Jeff and I discussed after his talk about considering using a different reserved word than where for the case when you're not bounding the type.

I understand there has already been a lot of bike shedding about what keyword to use for this, including discussions of for, with, etc. I don't mean to re-open any wounds, but this seems like a tidy change that might be worth making.


To help guide that discussion some, it might be worth considering when we expect users to use where T without bounds. Since there's the available syntactic sugar f{T,N}(x::Array{T,N}), it seems to me that the most useful time to use where T without a variable bound is for disambiguating scoping, such as this slide from Jeff's talk:

julia> (Vector{Vector{T}} where T) <: Vector{Vector{T} where T}
false

In that case, the purpose those where Ts serve is to scope which types can vary inside this type. So maybe something super explicit like withtype T, fortype T, withscope T, forscope T, scoped T, or something similar? Or maybe even two words (like we did with mutable struct), for type T, with type T?
Or, more ideas, maybe given T, under T, with T?

Unfortunately I don't have enough maths to be able to say "oh, it's like this one math thing", but maybe there's a good math term out there for this?

Sorry if I'm digging up an old conversation, but at least a couple people I talked to at the conference found the new syntax quite confusing, and I think this could help. Thanks for your time! :)

design speculative

Most helpful comment

Closing this issue. I think everyone is happy with the current state of the world, where where is spelled where. :)

All 18 comments

After playing a bit, I also see that the where T syntax is useful when you just want to evaluate a generic type as an expression:

julia> Tuple{A,B}
ERROR: UndefVarError: A not defined

julia> Tuple{A,B} where {A,B}
>> Tuple{A,B} where B where A

It feels like what you're saying here is "The type _Tuple_, parameterized on _A_ and _B_, with _A_ and _B_ as type variables".

Not sure how to capture all that in one word.

where T is just shorthand for where T<:Any, which reads like prose. It seems unnecessary to me to reserve another word for a case that's already a shorthand.

So I kind of agree with the OP. Probably 90% of the time in the wild, we're going to see naked where clauses (without the <:) and most users are not going to understand the etymology that it's short for where T<:Any. Without that knowledge, it does read quite cryptically.

I guess I don't find it that cryptic. We could definitely try to make it easier to locate in the documentation though.

(It's even stranger when one sees something like f(a::Array{T}) where T = 1, but in this case not even another word would solve it.)

For one-line function definitions, the preferred form is f(a::Array{T}) where {T} = 1, which provides better visual separation between the typevar and the function body.

I tend to like how

@exists T ->
function f(x::T, y::T)
   ...
end

reads, and I think this parses already, so one could make a macro to do this when things would look ugly with where. It also stacks very well, like in

@exists T ->
@exists (U <: AbstractArray{T}) ->
function f(x::U, y::T)
   ...
end

and can be used one-line in

@exists T -> f(x::T) = x

Of course this is extensible also to the syntax

@exists T -> Foo{T}

which is quite similar to the standard notation for this in type theory (since -> traditionally takes the place of . in Julia).

Finally, if βˆƒ parses as a unary macro, it can be used for this too. Then we can have βˆƒT -> f(x::T) = x πŸŽ‰.

Also,

function f(x::T, y::T) βˆ€ T
   ...
end
julia> (Vector{Vector{T}} βˆ€ T) <: Vector{Vector{T} βˆ€ T}
false

I like @NHDaly's "given"?

function f(x::T, y::T) given T 
    ... 
end

I dislike βˆ€ because UnionAll is the same idea as "there exists"; whereas βˆ€ would imply the intersection of all.

for example,

abstract type Animal{NumberOfLegs, NumberOfEyes} end
const Biped = Animal{2, NumberOfEyes} where NumberOfEyes

then

x isa Biped

means "there exists NumberOfEyes such that x is a Animal{2, NumberOfEyes}", not "for all NumberOfEyes, x is a Animal{2, NumberOfEyes}".

On the other hand, βˆƒ is perfectly consistent...

βˆƒ T -> f(x::T, y::T) = x + y

can read as "this method accepts arguments (x, y) if there exists T such that x isa T, y isa T".

For one-line function definitions, the preferred form isΒ f(a::Array{T}) where {T} = 1, which provides better visual separation between the typevar and the function body.

FWIW, I still have to re-read that once to parse it correctly. My initial parse is that where {T} = 1 is a single item, and then I have to remind myself that it isn't.

On the other hand, I'm not sure a better "single-word" version of where would help here. Maybe? But I'm not sure.

Another suggestion: for some T, or forsome T

forsome seems promising; it reads pretty well and avoids "stealing" a single word.

FWIW Crystal uses forall in this case.

Though I firmly maintain that I think we should stick to where. Changing the spelling for an already concise case is just churn, and adding an alternate spelling for the same thing just makes everything inconsistent. We just need to make sure that it's easy to figure out what where is and what it means in the documentation, just like any other language concept.

forsome reads as: for some T this is the case, for others it isn't. forall seems to imply that it is the case for all T, which it isn't, if there are other more specific methods. I like given, and given T<:MyType also reads really nicely. But to my non-native eye, where T parsed just fine, after I understood what it meant.

template<T, U> f(x::T, y::U) = ...

Just kidding :smile:

Closing this issue. I think everyone is happy with the current state of the world, where where is spelled where. :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

m-j-w picture m-j-w  Β·  3Comments

TotalVerb picture TotalVerb  Β·  3Comments

ararslan picture ararslan  Β·  3Comments

sbromberger picture sbromberger  Β·  3Comments

StefanKarpinski picture StefanKarpinski  Β·  3Comments