Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x as Foo: syntactic sugar for convert #8710

Closed
stevengj opened this issue Oct 17, 2014 · 40 comments
Closed

x as Foo: syntactic sugar for convert #8710

stevengj opened this issue Oct 17, 2014 · 40 comments
Labels
julep Julia Enhancement Proposal

Comments

@stevengj
Copy link
Member

convert is one of the most commonly used and important functions in Julia, with hundreds of methods, but it is a little ugly (and hard to type) to call explicitly. On #8648, the following syntactic sugar was proposed:

  • x as Foo: sugar for convert(Foo, x) (@quinnj) [or possibly convert(Foo, x)::Foo — see below]
  • foo(x as Foo) = ...: sugar for foo(x) = foo(convert(Foo, x)) and foo(x::Foo) = ... (@joehuchette)

Pros, cons?

@stevengj stevengj added the julep Julia Enhancement Proposal label Oct 17, 2014
@stevengj
Copy link
Member Author

As noted by @StefanKarpinski, one might also want to allow foo(x::Bar as Foo) = ... to be sugar for

foo(x::Bar) = foo(convert(Foo, x))
foo(x::Foo) = ...

This also extends in a natural way to parameterized definitions like foo{B,F}(x::Bar{B} as Foo{F}) = ... or foo{F}(x::Bar{F} as Foo{F}) = ...

@stevengj
Copy link
Member Author

We probably should also consider making x as Foo sugar for convert(Foo, x)::Foo, to force a Foo type (and to make sure the compiler knows the type).

@stevengj
Copy link
Member Author

@vtjnash suggests some additional cases:

F(x::Real as Int)
F(x::Real... as (Int...))
F(x as Int = 23 as Float32)

none of which seem to introduce any syntactic ambiguities. Note that convert already seems to work with type tuples:

julia> convert((Int...), (3.,4.,5.))::(Int...)
(3,4,5)

so little or no new functionality on the convert side seems required here.

@jakebolewski
Copy link
Member

2.0 as Int + 1.0 as Int does not seem very readable.

@StefanKarpinski
Copy link
Sponsor Member

This seems like allowing exactly one function call expression in a method signature, which makes it feel odd that there's something so special about this one function call. It really begs to be generalized somehow, and feels like it just might be useful for expressing the "Tim Holy Trait Trick" (THTT).

@timholy
Copy link
Sponsor Member

timholy commented Oct 17, 2014

(OT: That's the first time I know of something being named after me, with an acronym no less. Now I must be important. I'll get right to work seeing if I can persuade anyone else of that. 😄)

@joehuchette
Copy link
Member

as is nice because it reads like English and conveys the meaning well. It would be nice to be able to bind it to the operands without spaces, though (which would go a long way towards fixing @jakebolewski concerns, I think). Would 2.0-->Int + 1.0-->Int be too magical?

@StefanKarpinski
Copy link
Sponsor Member

we may want the arrow for arrow types some day.

@quinnj
Copy link
Member

quinnj commented Oct 18, 2014

I'm not sure I understand what else convert(Foo, x)::Foo tells the compiler over covert(Foo, x); anyone care to enlighten? Just an extra assert? Are there cases where covert(Foo,x) doesn't return something of type Foo?

@jakebolewski, I wouldn't see your example as a very good use case for this; in that case, I would just do Int(1.0) + Int(2.0). There are other cases where using as makes for much better readability.

Another thing that would be handy, but probably not very well generalizable would be able to do

x = [1, 2, 3, 4, 5]
x as Float64[]

@JeffBezanson
Copy link
Sponsor Member

The language doesn't enforce that convert(Foo, x) returns a Foo, so there is an outside chance that (1) the compiler won't know the return type is Foo, and/or (2) you might be surprised not to get a Foo, which would be caught by the extra type assertion.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 18, 2014

ref #1470, which also had quite a lengthy discussion about special syntax for convert (starting #1470 (comment) perhaps)

I prefer the english phrase. Perhaps I've been biased by python, but if a and b ... seems more generally readable than if a && b, at least until you've re-programmed your brain to recognize the second. Similarly, a as B seems more readable than a :> B, and it's really the same number of characters, since you should typically be putting whitespace around operators, for readability, anyways.

@prcastro
Copy link
Contributor

+1

@jakebolewski
Copy link
Member

I sympathize with using as over :> (and generally and over && and or over ||) but if we were to have an operator for this I would vote for :>. If we want :> to represent convert(T,x)::T then this seems to nicely dovetail specifying the return type of a function #1090.

function foo{T}(x::T) :> Int
   ....
end

over

function foo{T}(x::T) as Int
   ...
end

@StefanKarpinski
Copy link
Sponsor Member

Honestly, :> reads as "is supertype of" to me – i.e. the dual of <:. Arguably, the strongbad >: operator could be that, but I think that's less intuitive and just by analogy with <= and >= which isn't really a great analogy, in my opinion.

@johnmyleswhite
Copy link
Member

Instead of making as into infix sugar for convert, could we just rename convert to as?

@prcastro
Copy link
Contributor

I know it's one more keyword, but this looks nicer to my eyes

function foo{T}(x::T) returning Int
   ...
end

@StefanKarpinski
Copy link
Sponsor Member

IMO, "convert" is a much clearer name for this – "as" could mean either conversion or reinterpretation.

@jakebolewski
Copy link
Member

Good points. Imo I don't really think we need an operator at all. For the cases were you want to distinguish between construction and conversion (which in my experience are few) why not just call convert directly? It seems we are moving towards more ubiquitous use of constructors with Jeff's calloverload branch.

@StefanKarpinski
Copy link
Sponsor Member

I do feel like most conversion in Julia is implicit. I'm not sold on needing an operator.

@johnmyleswhite
Copy link
Member

IMO, "convert" is a much clearer name for this – "as" could mean either conversion or reinterpretation.

I'd say this argues against as in any role.

@StefanKarpinski
Copy link
Sponsor Member

Yep, I'm not a fan of as but I also do not have a better proposal for a conversion operator.

@JeffBezanson
Copy link
Sponsor Member

The coming ability to use T(x) for almost all convert-like purposes is a bit dangerous. Writing T(x) is more appealing than writing convert(T, x), and yet its meaning is basically "construct or convert", which is rather weird. It's hard to say precisely when it makes sense to use T(x) for an unknown T.

Of course the truly problematic cases would be rare, but a rare bug is the worst kind.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 20, 2014

Perhaps it could be said that for immutable types, they are equivalent operations; whereas for regular "container-like" types it is more typical for the two operations to be quite distinct. ?

EDIT:
perhaps:
write x as T if you something with the same "value" as x, but of (aka converted to) type T
write T(x) if you want something of type T that "contains" the object x

when T is an isbits, these definitions will tend to overlap, so the choice is unimportant. Otherwise, when T is unknown, it would seem that typically x as T would likely be a more correct choice

@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 20, 2014

IMO, "convert" is a much clearer name for this – "as" could mean either conversion or reinterpretation.

Are you referring to reinterpretation in the sense of type-casting? I don't think that ambiguity would be as much of a concern in a language that doesn't have interfaces, nor allow instantiation of abstract types. However, I think that it does read rather nicely in Gtk usage:

some_function( some_object as InterfaceType )

currently, that would typically be written as
some_function(InterfaceType(some_object))

which is somewhat less readable because it is hard to construct a mental sentence with the nouns in that order, whereas some_function(convert(InterfaceType, some_object)) requires a bit more typing, although the nouns are still in the wrong order (convert ... to ...) for trying to reconstruct the meaning of a piece of code

Instead of making as into infix sugar for convert, could we just rename convert to as?

I don't see why you couldn't as as both. Most julia operators work in both infix and prefix position, although mixing them in one expression can look a bit odd:
1+++(2,3) (aka 1+2+3)
as as as(T, S) (aka convert(convert(T, S), convert)

@JeffBezanson
Copy link
Sponsor Member

to isn't bad, as in "convert to": x to Int.

@StefanKarpinski
Copy link
Sponsor Member

to is pretty good.

@nalimilan
Copy link
Member

Looks like the discussion is going to repeat #1470 (the Frame(Frame()) example is always the problematic case). Would to/as allow to get rid of int() and friends? What about coercion?

@JeffBezanson
Copy link
Sponsor Member

I think Int() will replace int() one way or another.

I'm not sure about coercion; it seems if you try hard enough you can coerce just about anything to anything else.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 20, 2014

to is missing a verb, so for i = 1 to Int : 2 to Int reads very oddly

@nalimilan
Copy link
Member

This is a bit extreme. I guess you would write it for i = (1 to Int):(2 to Int), or use the function syntax.

@StefanKarpinski
Copy link
Sponsor Member

@nalimilan: What about coercion?

I've come to believe that coercion isn't a well-defined enough concept to deserve a generic function, let alone an operator. A good example is "coercion" of real values to integers – which way do you want to do it? Round, trunc, floor, ceil are all equally viable way to do this. Picking one arbitrarily is just asking for trouble. If you want truncation, use trunc; if you want rounding, use round.

@nalimilan
Copy link
Member

@StefanKarpinski I'm fine with that, apparently in #1470 the final conclusion was that it wasn't really needed. I just wanted to make sure the syntax discussed here would allow getting rid of int(), etc.

@stevengj
Copy link
Member Author

As @JeffBezanson points out, with the upcoming Foo(x) = convert(Foo, x) fallback from call overloading, maybe a convenient convert syntax will be superfluous. Although having the same syntax for function declarations is attractive.

@stevengj
Copy link
Member Author

I'm going to close this, as it doesn't seem to be a clear win.

@quinnj
Copy link
Member

quinnj commented Oct 20, 2014

As I was almost finished writing this, I'll post anyway, realizing the issue is already closed.

I think this discussion has gotten a little fractured. Here's a simplified proposal:

as{T}(x, ::Type{T}) = convert(T, x)::T
as(x, y) = as(x, typeof(y) )
# `as` is parsed as binary operator, allowing

# 111:     hdrlines = split(bytestring(buff, convert(Int, sz * n)), "\r\n")
111:     hdrlines = split(bytestring(buff, sz * n as Int), "\r\n")

# 143:     r = convert(Csize_t, b2copy)
143:     r = b2copy as Csize_t

# 186:     ret = convert(Cint, 0)
186:     ret = 0 as Cint

# 23: control_array = convert(Array{Uint8,1}, [vec(0:(parseint("1f", 16)))])
23: control_array = [vec(0:(parseint("1f", 16)))] as Uint[]

# 29:     _dbscan(D, convert(T, eps), minpts, 1:n)
29:     _dbscan(D, eps as T, minpts, 1:n)

(examples taken from my .julia/v0.4 directory)

There's certainly nothing too fancy about this proposal other than gaining some readability/simplicity, or in other words, sugar. I think it would be good to at least wait a while after the call branch is merged to let things shake out and get a feel for the new system, but this may be a convenience factor we consider at some point.

Obviously, one could write some very ugly/obfuscated code, but there's also no forcing of behavior here to use as; it's merely a convenience form.

@JeffBezanson
Copy link
Sponsor Member

as{T}(x, ::Type{T}) = convert(T, x)::T
as(x, y) = as(x, typeof(y) )

I just removed this behavior from oftype and I'd rather not go back there.

sz * n as Int

I wonder what the right precedence is.

@StefanKarpinski
Copy link
Sponsor Member

Operators that require spaces around them tend to seem like they should have loose precedence.

@garborg
Copy link
Contributor

garborg commented Oct 20, 2014

FWIW as suggested reinterpret to me, too.

On Mon, Oct 20, 2014 at 12:25 PM, Stefan Karpinski <notifications@github.com

wrote:

Operators that require spaces around them tend to seem like they should
have loose precedence.


Reply to this email directly or view it on GitHub
#8710 (comment).

@JeffBezanson
Copy link
Sponsor Member

To me, from a language geek perspective, x as T sounds more like a compile-time cast, e.g. that you'd use to make C++ pick an overload for a more general type. x to T suggests that something is being done to x's value, which is the case for us.

@nalimilan
Copy link
Member

Reminds me of Dim a() As Integer... :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
julep Julia Enhancement Proposal
Projects
None yet
Development

No branches or pull requests