Suggestion: Infer type from argument list

Currently, Swift can infer the type from .init followed by an argument list, like in line 9 below.

I think it would be natural, useful and non-problematic if we could skip the .init part, like in line 10 below (in contexts where it makes sense of course, ie where the .init(...) form is currently accepted).

Example:

struct Point {
    let x, y : Double
}
struct Circle {
    let center: Point
    let radius: Double
}
let a = Circle(center: Point(x: 1, y: 2), radius: 3) // OK
let b = Circle(center: .init(x: 1, y: 2), radius: 3) // OK
let c = Circle(center: (x: 1, y: 2), radius: 3) // Error, but couldn't it be OK as long as the tuple matches the argument list for an initializer of the expected type?


There are of course better real world examples where the benefit (of higher signal to noise ratio) is more obvious, but I wanted to keep the example simple.


What do you think?

I believe .init kind of works “accidentally” as a result of the rules about “Implicit Member Expressions” (the same thing that makes enum cases work as .Case in context), given that .inits were added later.


I would personally prefer a more explicit TupleLiteralConvertible protocol that allowed you to make things like

let p: Point = (1, 2)

work, or a labelled variant, instead of making every type indiscriminately convertible from every one of its initialisers. When you mix your proposal with type inference and more complex expressions, it's easy for me to imagine people e.g. accidentally instantiating types using their parameterless initialiser by assigning the result of a Void/() returning function. If you're going to consistently allow “single-element tuples” then you're also going to create a lot of confusion as to whether you're passing an integer to a function or the result of initialising some other type with that integer, and similar. At least the .init makes it clear that you're doing some sort of initialisation.

+1


The ".init(x: 1, y: 2)" in the OP's line #9 just seems weird to me, not that it's really all that relevant.


It sure would be nice to allow


p = (1, 2)


for p's of type Point in some way, though. That would solve the OP's issue and improve clarity in many situations.


Unfortunately, the current *LiteralConvertible protocol system doesn't seem flexible enough to handle the case of tuples, even if one might want to. Really, we're circling back to the issue of automatic type conversions generally. They were apparently in Swift at one point but were pulled before the FCS. I'm not sure if the problem was in the implementation, or whether the experience convinced the devs that this was something that Mankind Was Never Meant to Have. It does seem like a notable lacuna to me.

I'd like a TupleLiteralConvertible too, but I guess there are some complications or we would already have it.


I dont't think .init(...) is working just by accident, I think that I remember seeing something written about it in some release note but I'm not sure.


Of course there are cases where the type cannot be inferred without ambiguity, and in those cases the compiler will simply tell you about it, just like it already does in other similar situations. But in a lot of cases there is just one possible type (like in my Circle initializer example above).

Oh, by “accidentally” I meant that implicit member expressions have existed from the start, then they added .init much later so that you could could do useful things with storing initialisers, mapping arguments into them, etc. The combination of these two things means you can now write .init(parameters) as an implicit member expression as an emergent property of the interaction of those two things, rather than by deliberate design, in my opinion.


My caution was not about cases where the type cannot be inferred without ambiguity. The examples I gave were of accidentally using a parameterless initialiser or confusing passing an integer with passing something initialised with an integer. I feel like there would be a lot of potential for similar mistakes, which would compile just fine, unless you start restricting this to only initialisers with labelled arguments, and/or 2 or more parameters or something strange like that.

Ah, yes those are good points.

A currently working alternative is of course to use ArrayLiteralConvertible.

Here's an example using simd float2, which conforms to ArrayLitteralConvertible, in place of Point, and Triangle instead of Circle to make the benefit/need/noise-reduction more obvious:

import simd
struct Triangle {
    let p0, p1, p2 : float2
}
let ta = Triangle(p0: float2(x: 0, y: 0), p1: float2(x: 0, y: 0), p2: float2(x: 0, y: 0))
let tb = Triangle(p0: [0, 0], p1: [0, 1], p2: [1, 0])


The down side is the lack of compile time check of the number of elements in the array literal, and the performance overhead that goes with it (I presume).

Yeah. I would love a TupleLiteralConvertible or similar for cases like these. If it existed, you could presumably make

let t: Triangle = ((0, 0), (0, 1), (1, 0))

work too, if you wanted to.

Suggestion: Infer type from argument list
 
 
Q