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

RFC: Add x as <type> syntax for conversion. #15818

Closed
wants to merge 1 commit into from

Conversation

iamed2
Copy link
Contributor

@iamed2 iamed2 commented Apr 9, 2016

With this PR: (12 as Float64) === 12.0

Very similar to what Rust does.

I copied what was done with in on the parser side but that might be the wrong place for it?

Would people like this? I certainly would find it a useful syntactic sugaring. I looked for previous proposals for convert syntax but couldn't find any, but I apologize if this has already been discussed.

@tkelman tkelman added the needs tests Unit tests are required for this change label Apr 9, 2016
@bicycle1885
Copy link
Member

IMHO, Float64(12) does the same thing and is more concise than 12 as Float64.

@iamed2
Copy link
Contributor Author

iamed2 commented Apr 10, 2016

The constructor can have additional behaviour that may not be desired, i.e. Array{Float64}(3) does not do the same thing as 3 as Array{Float64}.

@tkelman I made a simple doctest in the manual. What tests would you like to see? Testing behaviour (i.e. same as convert) or precedence?

@vtjnash
Copy link
Member

vtjnash commented Apr 10, 2016

I like this, because I think it could be generalized well to other places where convert would be useful, such as return types : function foo(x) as Int; return bar() ? x : 1; end (#1090)

and argument types : function foo(x as Int); return bar() ? x : 1; end (helping the compiler minimize code overspecialization and improving test coverage analysis, while making APIs more duck-type flexible)

@tkelman
Copy link
Contributor

tkelman commented Apr 10, 2016

Should be tested as thoroughly as possible without making the tests noticeably slower. Usage, behavior, different parsing contexts, etc. Like any new feature.

@eschnett
Copy link
Contributor

The current function oftype (now convert) does this. That is, the
functionality is there, this proposal just suggests a new syntax.


*julia> **convert(Float64, 1)*

*1.0*


*julia> **convert(Vector{Float64}, [1])*

*1-element Array{Float64,1}:*

* 1.0*

-erik

On Sun, Apr 10, 2016 at 5:43 AM, Tony Kelman notifications@github.com
wrote:

Should be tested as thoroughly as possible without making the tests
noticeably slower. Usage, behavior, different parsing contexts, etc. Like
any new feature.


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
#15818 (comment)

Erik Schnetter schnetter@gmail.com
http://www.perimeterinstitute.ca/personal/eschnetter/

@nalimilan
Copy link
Member

@eschnett oftype(x, y) is equivalent to convert(typeof(x), y), not a replacement for convert. And @iamed2 is aware of the existence of convert, given that he uses it in the PR.

@stevengj
Copy link
Member

stevengj commented May 7, 2016

I'm not convinced of the need for a new keyword here. In the vast majority of cases, T(x) is equivalent to convert(T, x) and is shorter than x as T. In the few remaining cases like Array, it's not that onerous to call convert(T, x).

@vtjnash
Copy link
Member

vtjnash commented May 7, 2016

In the vast majority of cases

I think we're finding that this is not true, and slowing backing off on that experiment (and revising the call -> convert rule). The main benefit I still see here is that it adds a way to make it easy to convert early to the intended type (without worrying if call and convert happen to be the same) and making it clear what the author intended. Plus, making sure there aren't unnecessary types around can make it easier on the compiler so it can do it's job faster and get out of the way.

@stevengj
Copy link
Member

stevengj commented May 7, 2016

@vtjnash, aren't most conversions in practice from one numeric type to another, or from one collection type to another? And in these cases T(x) works. Do you have any data to the contrary?

@vtjnash
Copy link
Member

vtjnash commented May 26, 2016

Yes. But my point is that conflating the concepts works pretty well ... until it doesn't, and then it can be nice to have an explicit syntax instead of relying on the implicit form.

ref #15120

@anthonyclays
Copy link
Contributor

I very much like the as syntax in Rust, and I think thing() as Float64 expresses your intention much clearer than Float64(thing()) (I have in fact already caught myself writing thing() |> Float64).

@vtjnash While using the as syntax for function arguments (as in function foo(x as Int); return bar() ? x : 1; end) seem like a very nice idea at a first glance, consider the case where you want the argument to dispatch on the abstract type Integer: function foo(x::Integer as Int); return bar() ? x : 1; end

Including magic like x::Integer as Int means introducing one more 'gotcha' in the language that will make it harder for beginners, and I don't know if it's worth it in this case.

@ma-laforge
Copy link
Contributor

@anthonyclays: including magic like x::Integer as Int means introducing one more 'gotcha' in the language that will make it harder for beginners, and I don't know if it's worth it in this case.

Not convinced this is an accurate statement. Most people are already familiar with the idea of the "compiled type". The thing that is more alien to new developpers is probably the ::Integer portion of things (Which is key to Julia's power). And, for myself the jump was figuring out that "::" wasn't a scope resolution operator.

If your issue is with the as keyword, maybe an alternative syntax would be better, for example:

#myfunction(x::DispatchType::CompileType) = ...
f(x::Real::Float64) = x^50

(Taken from "Type seperation & code bloat: dispatch vs compile type."
https://groups.google.com/forum/#!topic/julia-dev/pfLoeBa2VFM )

...But I honestly do not hate the "as" notation.

@StefanKarpinski
Copy link
Member

In function signatures, this syntax would work:

f(n::Integer=>Int) = 2n + 1

Which would be shorthand for this pair of definitions:

f(n::Integer) = f(Int(n))
f(n::Int) = 2n + 1

The syntax isn't applicable in other contexts, but in those cases, I feel like calling convert is ok.

@eschnett
Copy link
Contributor

This sounds similar to what promote is currently doing, except that promoting compares several types, whereas here there's only a single type. Could this be unified?

@kshyatt kshyatt added the types and dispatch Types, subtyping and method dispatch label Jul 28, 2016
@TotalVerb
Copy link
Contributor

TotalVerb commented Sep 15, 2016

I think it would be nice if we can do this more generally, instead of just making it syntax sugar for convert. There are a lot of cases where it would be nice to dispatch on "return type", and currently the convention is to just put the type first. What about a function like:

as{T}(::Type{T}, f, xs...) = convert(T, f(xs...))

that can be overloaded to allow things like parse("100") as Int (becomes as(Int, parse, "100") = parse(Int, "100")), collect(itr) as Vector{Int} (becomes as(Vector{Int}, collect, itr) = collect(Int, itr)). I feel like func(T, x) syntax is used often enough that this sugar would be useful. This could even solve some complaints about precision: 2^1000 as BigInt could become as(BigInt, ^, 2, 1000), which can promote arguments before doing the computation.

Kind of like fused broadcast. It's an optimization in most cases and could affect observable behaviour in some cases, but only when that makes sense.

A similar thing can probably be done for local variables with type annotations. Then someone can do x::UInt8; y::UInt8; sum::Int = x + y and get the C-like behaviour of promoting before computing.

@KristofferC
Copy link
Member

Feels very similar do the do syntax.

@TotalVerb
Copy link
Contributor

I was thinking about ways to (say) get the first 10 graphemes of a string as a String. The most sensible way to do it without writing a loop manually is String(collect(Base.flatten(take(graphemes(str), n)))), but this has a lot of unnecessary copies, and a lot of noise. Such a problem can be fixed with a Base.flatten(take(graphemes(str), n)) as String syntax.

@quinnj
Copy link
Member

quinnj commented Oct 19, 2016

FWIW, I just came across the Swift as semantics today, which include:

  • x as? T: which tries to convert x to T, resulting in nil if the conversion fails
  • x as! T: which tries to convert x to T, resulting in a runtime error if conversion fails

Note that these are the only two versions, i.e. you can only do a weak (Nullable) or forceful convert. I kind of like this distinction, because there's been quite a few back and forths on our own convert behavior and whether we need a coerce function, etc. I also think having x as? T => Nullable{T} would be convenient in a lot of cases, such as having keyword arguments automatically call x as? T. That would allow for a method definition like

f(x; optional_kwarg::Nullable{Int}=Nullable{Int}())

be called like

f(x; optional_kwarg=1)

Anyway, just a few thoughts here.

@JeffBezanson JeffBezanson added parser Language parsing and surface syntax and removed types and dispatch Types, subtyping and method dispatch labels Apr 4, 2018
@iamed2 iamed2 closed this Aug 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs tests Unit tests are required for this change parser Language parsing and surface syntax
Projects
None yet
Development

Successfully merging this pull request may close these issues.