Make "tuple syntax" a shorthand for initializing a contextual type

I think I'm mildly in favor with a preceding . or $.

That said, I do think this is confusing from a new user perspective and it does still decrease clarity when working in a new codebase, so I think I would generally still avoid using it.

I almost like it more as a cleaner way to do Type conversion.

let myInt = 5
let myFloat: Float = .(myInt)

let myArray = [1, 2, 3, 4]
let mySet: Set<Int> = .(myArray)

I personally think that those cases would be better served by something like from|to than tuple shorthand.

1 Like

Silly question...

Allowing .init(...something...) as (...something...), is it the responsibility of:

  • the compiler for all init()
  • for a type (SIMD2<Float>) to allow it for all or some of its init() to be called in such way
  • for the specific APIs (including init()) to suggest that it would make sense for all (or some) of its arguments to be "tuple-izable"

So if Rect<Float>.init(origin: SIMD2<Float>, size: SIMD2<Float>) is marked as being callable with tuple-like syntax for all its argument (and its the only API marked as such), we get:

var s1 : SIMD2<Float> = .init(x:0.0, y:0.0) // existing init
var s2 : SIMD2<Float> = .init(0.0, 0.0) // existing init
var s3 : SIMD2<Float> = (x:0.0, y:0.0) // not ok (only Rect<Float>.init marked)
Rect<Float>(origin: .init(0.0, 0,0), size: .init(10.0, 10.0)) // current still ok
Rect<Float>(origin: (0.0, 0,0), size: (10.0, 10.0)) // ok
Rect<Float>(origin: (x:0.0, y:0,0), size: (x:10.0, y:10.0)) // ok
foo(bounds: (origin: (1, 2), size: (3, 4)), barPoint: (5, 6)) // not ok (only Rect<Float>.init marked)
foo(bounds: Rect<Float>(origin: (1, 2), size: (3, 4)), barPoint: SIMD2<Float>(5, 6)) // ok

I've just found myself writing CGPoint's .init for the hundredth time today, and, long story short, I would love to have this feature :slight_smile: Although, I'm absolutely not a fan of throwing syntactic sugar all over the place, even if it is makes my coding bit easier.

What we can do here is to introduce something like plain old data transfer objects, that will have ability to be constructible from tuples, to be deconstructible to tuples and have guarantee that deconstruction to tuple A and construction from same tuple A will result to indistinguishable objects. Something like:

struct Point: DTOish {
  var x, y: Float
}
let p = Point(2, 3)
let deconstructed: (Float, Float) = p.deconstructed
let new = Point(deconstructed)
// also:
func length(p: Point) { sqrt(p.x*p.x + p.y*p.y) }
lenght(p: (x: 2, y: 3)) // implicit construction

I think that addition looks much more coherent (is that the right word?), adding not only syntactic sugar but some higher meaning, reducing the barrier between tuples and structs.

It might be useful in many other places, for example, lets say I have some set of options for label displayed to user:

struct Opts {
  var color: Color
  var text: String
}
func display(with opts: Opts) {
  label.color = opts.color
  label.text = text
}

If I add new options, size, I will need to update every place where that options are used, to not forget about new option. With deconstruction compiler can warn me:

func display(with opts: Opts) {
  // Error when additional option would be added
  (label.color, label.text) = opts.deconstructed
}

Sorry for being a bit off-topic, but I honestly think that those thing should move together, and tuple deconstruction is such a great feature, that it is absolutely should be available for structs too :slight_smile:

This DTOish-approach doesn't solve the single-argument case though? And seems to be very close to the idea of an ExpressibleByTupleLiteral protocol, which has its own problems (see previous discussions).

Regarding the single-argument roadblock of the shorthand as pitched. I think I've never actually felt the need for it to work with single-argument inits, so from a purely practical point of view, I'd prefer option 2 here:

Although I'm guessing that it wouldn't be accepted, because its non-existence for the single argument case would make it too strange.

One could argue that it's just following the example of tuples though (they are non-existent for the single element case).

As I see it, they've always been optional.

var point: CGPoint? = .init(x: 0, y: 1)
point = Optional( (0, 1) ).map(CGPoint.init)
point = [(0, 1)].map(CGPoint.init).first!

I was against closures losing support for argument labels. But I think it's been demonstrated that argument labels aren't a priority for the language, past the imperative cases. I think it's inconsistent to not extend the power to eliminate them in low-order functions.

let point = { $0(0, 1) }(CGPoint.init)

infix operator •
func •<A, R>(lhs: (A) -> R, rhs: A) -> R { return lhs(rhs) }

let point = CGPoint.init • (0, 1)
1 Like