Draft proposal: multi-property assignment .= operator


(Michel Fortin) #1

(This proposal came from thinking about the memberwise initializer proposal as well as older proposals for "cascading" and creating scopes making some members act like local variables.)

I'd like to propose a syntax to set multiple properties at once. It would look like this:

  var object = MyObject()
  object .= (
    property1: 1,
    property2: "a"
  )

and be equivalent to this:

  var object = MyObject()
  object.property1 = 1
  object.property2 = "a"
  
What the `.=` operator does is take each value in the tuple on the right and assign them to the property of the same name on the variable on the left. Assignments are performed in the same order as they're defined in the tuple.

The tuple on the left of the `.=` operator can be written on the spot (as above) or be the result of an arbitrary expression, like here:

  var object = MyObject()

  var values = (property1: 1, property2: "b")
  object .= values

  func makeValuesFor(value: Int) -> (property1: Int, property2: String) {
    return (value, "\(value)")
  }
  object .= makeValuesFor(4)

If the tuple contains property names that do not exist in the assigned variable, or if there is a mismatch in type or visibility and the assignment cannot happen, this is a compile-time error.

This syntax is particularly beneficial when assigning to properties of a deeply nested value:

  object.subpart.detail.numberPad .= (
    radix: 9
    position: .Top
    font: .System
  )

## Tentative Implementation

It's almost possible already to implement this with reflection. Here's an attempt (using a different operator name because `.=` doesn't work as a custom operator):

  infix operator ~= { }

  func ~= <T>(inout target: T, values: (a: Int, b: String)) {
    let valuesMirror = Mirror(reflecting: values)
    let targetMirror = Mirror(reflecting: target)
    valueLoop: for valueField in valuesMirror.children {
      guard let label = valueField.label else {
        fatalError("Missing label in value tuple.")
      }
      for targetField in targetMirror.children {
        print(targetField)
        if targetField.label == label {
          targetField.value = valueField.value
          continue valueLoop
        }
      }
    }
  }

This fails because you can't assign to fields using the Mirror API. If this line could be replaced by something that works:

  targetField.value = valueField.value

then we could have a library implementation.

···

--
Michel Fortin
https://michelf.ca


(Douglas Gregor) #2

(This proposal came from thinking about the memberwise initializer proposal as well as older proposals for "cascading" and creating scopes making some members act like local variables.)

I'd like to propose a syntax to set multiple properties at once. It would look like this:

  var object = MyObject()
  object .= (
    property1: 1,
    property2: "a"
  )

and be equivalent to this:

  var object = MyObject()
  object.property1 = 1
  object.property2 = "a"
  
What the `.=` operator does is take each value in the tuple on the right and assign them to the property of the same name on the variable on the left. Assignments are performed in the same order as they're defined in the tuple.

For me, the scale of the problem isn’t large enough to warrant a language feature, especially given that...

## Tentative Implementation

It's almost possible already to implement this with reflection. Here's an attempt (using a different operator name because `.=` doesn't work as a custom operator):

  infix operator ~= { }

  func ~= <T>(inout target: T, values: (a: Int, b: String)) {
    let valuesMirror = Mirror(reflecting: values)
    let targetMirror = Mirror(reflecting: target)
    valueLoop: for valueField in valuesMirror.children {
      guard let label = valueField.label else {
        fatalError("Missing label in value tuple.")
      }
      for targetField in targetMirror.children {
        print(targetField)
        if targetField.label == label {
          targetField.value = valueField.value
          continue valueLoop
        }
      }
    }
  }

This fails because you can't assign to fields using the Mirror API. If this line could be replaced by something that works:

  targetField.value = valueField.value

then we could have a library implementation.

Assigning to fields using the Mirror API is a completely reasonable feature to add.

  - Doug

···

On Jan 10, 2016, at 12:21 PM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:


(Tino) #3

Methods like "setValuesForKeysWithDictionary" can indeed be very handy — but its name is quite verbose, and imho that isn't that bad:
It's hard to guess the meaning of ".=" (I'd prefer "<-" instead, but I guess depending on the background of the reader, this may be as hard to understand)

A method with the functionality would be nice, and the possibility of compile time checking would make it better than the variant with dictionaries — but to make the check working, this method would need special treatment by the compiler, and that would appear strange to me.

You've written about the older proposal sketches for cascading; have you seen the following idea?
object.{
  property1 = 1
  property2 = "a"
}
It is a less flexible in some aspect (the tuple in your draft could be created by a function call*), but is even more concise (not much, though) and can be used for more than assigning properties.

Best regards,
Tino

* If you continue with your draft, I suggest to explore this possibility further: With a function to turn members of an existing object into a tuple, or the option to selectively remove elements from a tuple, it could be a handy way to create copies.


(Jacob Bandes-Storch) #4

I would much prefer to see something like a "with" construct, which has
been discussed previously in other threads. It would afford
property-setting and also method calls, allowing people to build DSLs
pretty easily. I don't feel that a multi-setter is particularly valuable as
a special case.

object.{ // or "with object {"
    property1 = 1
    property2 = "a"
    method()
    ...
}

Jacob Bandes-Storch

···

On Sun, Jan 10, 2016 at 12:21 PM, Michel Fortin via swift-evolution < swift-evolution@swift.org> wrote:

(This proposal came from thinking about the memberwise initializer
proposal as well as older proposals for "cascading" and creating scopes
making some members act like local variables.)

I'd like to propose a syntax to set multiple properties at once. It would
look like this:

        var object = MyObject()
        object .= (
                property1: 1,
                property2: "a"
        )

and be equivalent to this:

        var object = MyObject()
        object.property1 = 1
        object.property2 = "a"

What the `.=` operator does is take each value in the tuple on the right
and assign them to the property of the same name on the variable on the
left. Assignments are performed in the same order as they're defined in the
tuple.

The tuple on the left of the `.=` operator can be written on the spot (as
above) or be the result of an arbitrary expression, like here:

        var object = MyObject()

        var values = (property1: 1, property2: "b")
        object .= values

        func makeValuesFor(value: Int) -> (property1: Int, property2:
String) {
                return (value, "\(value)")
        }
        object .= makeValuesFor(4)

If the tuple contains property names that do not exist in the assigned
variable, or if there is a mismatch in type or visibility and the
assignment cannot happen, this is a compile-time error.

This syntax is particularly beneficial when assigning to properties of a
deeply nested value:

        object.subpart.detail.numberPad .= (
                radix: 9
                position: .Top
                font: .System
        )

## Tentative Implementation

It's almost possible already to implement this with reflection. Here's an
attempt (using a different operator name because `.=` doesn't work as a
custom operator):

        infix operator ~= { }

        func ~= <T>(inout target: T, values: (a: Int, b: String)) {
                let valuesMirror = Mirror(reflecting: values)
                let targetMirror = Mirror(reflecting: target)
                valueLoop: for valueField in valuesMirror.children {
                        guard let label = valueField.label else {
                                fatalError("Missing label in value tuple.")
                        }
                        for targetField in targetMirror.children {
                                print(targetField)
                                if targetField.label == label {
                                        targetField.value =
valueField.value
                                        continue valueLoop
                                }
                        }
                }
        }

This fails because you can't assign to fields using the Mirror API. If
this line could be replaced by something that works:

        targetField.value = valueField.value

then we could have a library implementation.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Angelo Villegas) #5

I think the "<-" operator is better than ".=". This and cascade style assignment would be a good team for me.
object <- { prop1: value1, prop2: value2}

···

_____________________________
From: Tino Heth via swift-evolution <swift-evolution@swift.org>
Sent: Monday, January 11, 2016 7:12 AM
Subject: Re: [swift-evolution] Draft proposal: multi-property assignment .= operator
To: Michel Fortin <michel.fortin@michelf.ca>
Cc: swift-evolution <swift-evolution@swift.org>

Methods like "setValuesForKeysWithDictionary" can indeed be very handy — but its name is quite verbose, and imho that isn't that bad:
It's hard to guess the meaning of ".=" (I'd prefer "<-" instead, but I guess depending on the background of the reader, this may be as hard to understand)

A method with the functionality would be nice, and the possibility of compile time checking would make it better than the variant with dictionaries — but to make the check working, this method would need special treatment by the compiler, and that would appear strange to me.

You've written about the older proposal sketches for cascading; have you seen the following idea?
object.{
  property1 = 1
  property2 = "a"
}
It is a less flexible in some aspect (the tuple in your draft could be created by a function call*), but is even more concise (not much, though) and can be used for more than assigning properties.

Best regards,
Tino

* If you continue with your draft, I suggest to explore this possibility further: With a function to turn members of an existing object into a tuple, or the option to selectively remove elements from a tuple, it could be a handy way to create copies.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michel Fortin) #6

If it can be done using purely library implementation, that's fine too. I have doubts you can make everything that should be a compile-time error if going that route (notice the runtime fatal error above), or get decent auto-completion. For that, we'd have to write a generic constraint that all the properties on the right exists on the left hand side with a compatible type, which is not expressible currently (as far as I know).

That said, I also realize now that the Mirror API does not do what I need here. I don't want to access "fields" (aka. stored properties) of the target object, I need to access all the user-exposed properties having the proper visibility in the current context, which includes computed properties that have a setter. Mirror is good for walking the tuple, but not the target.

···

Le 10 janv. 2016 à 22:29, Douglas Gregor <dgregor@apple.com> a écrit :

On Jan 10, 2016, at 12:21 PM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

(This proposal came from thinking about the memberwise initializer proposal as well as older proposals for "cascading" and creating scopes making some members act like local variables.)

I'd like to propose a syntax to set multiple properties at once. It would look like this:

  var object = MyObject()
  object .= (
    property1: 1,
    property2: "a"
  )

and be equivalent to this:

  var object = MyObject()
  object.property1 = 1
  object.property2 = "a"
  
What the `.=` operator does is take each value in the tuple on the right and assign them to the property of the same name on the variable on the left. Assignments are performed in the same order as they're defined in the tuple.

For me, the scale of the problem isn’t large enough to warrant a language feature, especially given that...

## Tentative Implementation

It's almost possible already to implement this with reflection. Here's an attempt (using a different operator name because `.=` doesn't work as a custom operator):

  infix operator ~= { }

  func ~= <T>(inout target: T, values: (a: Int, b: String)) {
    let valuesMirror = Mirror(reflecting: values)
    let targetMirror = Mirror(reflecting: target)
    valueLoop: for valueField in valuesMirror.children {
      guard let label = valueField.label else {
        fatalError("Missing label in value tuple.")
      }
      for targetField in targetMirror.children {
        print(targetField)
        if targetField.label == label {
          targetField.value = valueField.value
          continue valueLoop
        }
      }
    }
  }

This fails because you can't assign to fields using the Mirror API. If this line could be replaced by something that works:

  targetField.value = valueField.value

then we could have a library implementation.

Assigning to fields using the Mirror API is a completely reasonable feature to add.

--
Michel Fortin
https://michelf.ca


(Michel Fortin) #7

Methods like "setValuesForKeysWithDictionary" can indeed be very handy — but its name is quite verbose, and imho that isn't that bad:
It's hard to guess the meaning of ".=" (I'd prefer "<-" instead, but I guess depending on the background of the reader, this may be as hard to understand)

I choose `.=` because it's an assignment (hence `=`) to member properties (hence `.`). What is the meaning of `<-`?

A method with the functionality would be nice, and the possibility of compile time checking would make it better than the variant with dictionaries — but to make the check working, this method would need special treatment by the compiler, and that would appear strange to me.

What I'm proposing is to use a tuple, not a dictionary. With a tuple, the "keys" are always known at compile time because they're part of the type.

You've written about the older proposal sketches for cascading; have you seen the following idea?
object.{
  property1 = 1
  property2 = "a"
}

It is a less flexible in some aspect (the tuple in your draft could be created by a function call*), but is even more concise (not much, though) and can be used for more than assigning properties.

Sure I've seen it. It's a completely different concept though: the braces suggest that you have code inside, not a list of values. Because you're bringing all the members in the local scope in that block, it makes unintended ambiguities and/or shadowing inside this code much more likely (you already have global + self members + local scope variables, now you're adding member variables from elsewhere that can shadow even your local variables). I'm sure I pointed this out before in an older thread. The tuple-assignement version has no such problem.

* If you continue with your draft, I suggest to explore this possibility further: With a function to turn members of an existing object into a tuple, or the option to selectively remove elements from a tuple, it could be a handy way to create copies.

I probably won't pursue it further since the general response was not much positive.

···

Le 10 janv. 2016 à 18:12, Tino Heth <2th@gmx.de> a écrit :

--
Michel Fortin
https://michelf.ca


(Jo Albright) #8

I don’t know if this is too different from what you are wanting to do. But this can done with Swift now.

https://github.com/joalbright/Inlinit (can be used as a CocoaPod or with SwiftPM)

struct Person: Inlinit {

    var age: Int = 0
    var name: String?

}

// initialize & set properties
var me = Person {

    $0.name = "Jo"
    $0.age = 32

}

// update properties
me <- {

    $0.age = 30
    $0.name = "John"

}

Designer . Developer .  Nerd
Jo Albright

···

On Jan 10, 2016, at 10:05 PM, Angelo Villegas via swift-evolution <swift-evolution@swift.org> wrote:

I think the "<-" operator is better than ".=". This and cascade style assignment would be a good team for me.

object <- {
    prop1: value1,
    prop2: value2
}
_____________________________
From: Tino Heth via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
Sent: Monday, January 11, 2016 7:12 AM
Subject: Re: [swift-evolution] Draft proposal: multi-property assignment .= operator
To: Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>>
Cc: swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>

Methods like "setValuesForKeysWithDictionary" can indeed be very handy — but its name is quite verbose, and imho that isn't that bad:
It's hard to guess the meaning of ".=" (I'd prefer "<-" instead, but I guess depending on the background of the reader, this may be as hard to understand)

A method with the functionality would be nice, and the possibility of compile time checking would make it better than the variant with dictionaries — but to make the check working, this method would need special treatment by the compiler, and that would appear strange to me.

You've written about the older proposal sketches for cascading; have you seen the following idea?
object.{
property1 = 1
property2 = "a"
}
It is a less flexible in some aspect (the tuple in your draft could be created by a function call*), but is even more concise (not much, though) and can be used for more than assigning properties.

Best regards,
Tino

* If you continue with your draft, I suggest to explore this possibility further: With a function to turn members of an existing object into a tuple, or the option to selectively remove elements from a tuple, it could be a handy way to create copies.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jesse Rusak) #9

For what it’s worth, I think this is a good idea. I actually think that this is a nice stepping stone towards general solutions which help to reduce boilerplate, e.g. in initializers. For example, if you could refer to the arguments passed to the current function with some keyword (as in “arguments” in JS) then you could define relatively-concise and flexible initializers with something like:

struct Foo {
   let a: Int
   let b: String

   init(a: Int, b: String = “") {
      self .= $arguments
   }
}

In this case I think the “.=” would have to be a language feature in order to allow that assignment to statically satisfy the initialization requirements for a & b.

- Jesse

···

On Jan 11, 2016, at 8:40 AM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

* If you continue with your draft, I suggest to explore this possibility further: With a function to turn members of an existing object into a tuple, or the option to selectively remove elements from a tuple, it could be a handy way to create copies.

I probably won't pursue it further since the general response was not much positive.


(Tino) #10

What is the meaning of `<-`?

It's an assignment-operator in some other languages (S/R, maybe more I'm not aware of).
Those languages don't use the "arrow" like C++ does, but to express that data should be stored:
calculate(input) -> output
output <- calculate(input)
have the same effect.