"with" operator a la O'Caml?


(Andy Chou) #1

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
    let name: String
    let address: String
    let phone: String
}

func f() {
    let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
    let chris = andy.with(name: "Chris")
    let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy


(Andy Chou) #2

Of course. Thanks for pointing out the obvious solution. This preserves the immutability of the struct and doesn't require O(n^2) code for structs with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic initializer that does what your solution does. But that may be overkill given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable structs. I wasted too much time trying to solve this, I suspect others would just give up and use var, or even classes.

Andy

···

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com> wrote:

You are probably asking for a generic solution, but for a specific struct you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String? = nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution <swift-evolution@swift.org>, wrote:
I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
let name: String
let address: String
let phone: String
}

func f() {
let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
let chris = andy.with(name: "Chris")
let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

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


(Derrick Ho) #3

struct Person {
Var name: String = ""

func with(name n: String) -> Person {
var a = self
a.name = name
return a
}
}

let andy = Person().with(name: "Andy")
let brandon = andy.with(name: "Brandon")

···

On Mon, Dec 19, 2016 at 10:28 AM Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

I like that structs are value types in Swift, this encourages the use of
immutable data. O'Caml has an operator "with" that allows for copying an
existing struct with a change to one field. I looked at Lenses for this
functionality and it seems like a lot to digest for something so simple. I
also tried to implement this using a constructor, or a function, and it was
not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this
syntax):

struct Person {
    let name: String
    let address: String
    let phone: String
}

func f() {
    let andy = Person(name: "Andy", address: "1 Battery St., San
Francisco, CA", phone: "1234567")
    let chris = andy.with(name: "Chris")
    let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments
cannot reference properties of self. Same problem trying to do this in a
constructor.

Obviously it's possible to create an entirely new Person specifying the
values from an existing Person, but this is very tedious with structures
with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

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


(Rien) #4

A little more work, but I like this pattern.

struct Person {
    
    let name: String
    let address: String
    let phone: String
    
    func name(_ n: String) -> Person {
        return Person(name: n, address: self.address, phone: self.phone)
    }
    
    func address(_ a: String) -> Person {
        return Person(name: self.name, address: a, phone: self.phone)
    }

    func phone(_ p: String) -> Person {
        return Person(name: self.name, address: self.address, phone: p)
    }
}

let p1 = Person(name: "Andrew", address: "Milkyway", phone: "031234")

let p2 = p1.name("Bart")

let p3 = p2.name("Jack").address("Earth”)

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Swiftrien
Project: http://swiftfire.nl

···

On 18 Dec 2016, at 02:40, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
   let name: String
   let address: String
   let phone: String
}

func f() {
   let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
   let chris = andy.with(name: "Chris")
   let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

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


(Slava Pestov) #5

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
   let name: String
   let address: String
   let phone: String
}

func f() {
   let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
   let chris = andy.with(name: "Chris")
   let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

What’s the advantage of this over declaring the stored properties of the struct ‘var’ and then doing this?

var chris = andy
chris.name = “Chris"

Slava

···

On Dec 17, 2016, at 8:40 PM, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

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


(Erica Sadun) #6

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

···

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves the immutability of the struct and doesn't require O(n^2) code for structs with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic initializer that does what your solution does. But that may be overkill given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable structs. I wasted too much time trying to solve this, I suspect others would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com <mailto:nick.keets@gmail.com>> wrote:

You are probably asking for a generic solution, but for a specific struct you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String? = nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>, wrote:

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
let name: String
let address: String
let phone: String
}

func f() {
let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
let chris = andy.with(name: "Chris")
let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________
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


(Andy Chou) #7

Thanks Erica, I wasn't aware of that Swift evolution proposal. If I'm reading it right, this wouldn't work with structs with let-variables...? Here's what I get with this example:

struct Person {
    let name: String
    let address: String
}

@discardableResult
public func with<T>(_ item: T, update: (inout T) throws -> Void) rethrows -> T {
    var this = item
    try update(&this)
    return this
}

let john = Person(name: "John", address: "1 battery st")
let jane: Person = with(john) { $0.name = "Jane" } // Test.swift:24:41: Cannot assign to property: 'name' is a 'let' constant

Andy

···

On Dec 19, 2016, at 11:44 AM, Erica Sadun <erica@ericasadun.com> wrote:

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves the immutability of the struct and doesn't require O(n^2) code for structs with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic initializer that does what your solution does. But that may be overkill given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable structs. I wasted too much time trying to solve this, I suspect others would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com <mailto:nick.keets@gmail.com>> wrote:

You are probably asking for a generic solution, but for a specific struct you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String? = nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>, wrote:

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
let name: String
let address: String
let phone: String
}

func f() {
let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
let chris = andy.with(name: "Chris")
let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andy Chou) #8

Thanks Erica, I wasn't aware of that proposal. If I'm reading it right, the proposed 'with' function won't work for let-constants in structures, e.g.:

struct Person {
    let name: String
    let address: String
}

@discardableResult
public func with<T>(_ item: T, update: (inout T) throws -> Void) rethrows -> T {
    var this = item
    try update(&this)
    return this
}

let john = Person(name: "John", address: "1 battery st")
let jane: Person = with(john) { $0.name = "Jane" } // Cannot assign to property: 'name' is a 'let' constant

Andy

···

On Dec 19, 2016, at 11:44 AM, Erica Sadun <erica@ericasadun.com> wrote:

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves the immutability of the struct and doesn't require O(n^2) code for structs with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic initializer that does what your solution does. But that may be overkill given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable structs. I wasted too much time trying to solve this, I suspect others would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com <mailto:nick.keets@gmail.com>> wrote:

You are probably asking for a generic solution, but for a specific struct you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String? = nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>, wrote:

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {
let name: String
let address: String
let phone: String
}

func f() {
let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")
let chris = andy.with(name: "Chris")
let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Martin Waitz) #9

-1
I don't like where this is heading.

If you want to introduce method cascading, then have a look at Dart.

E.g. the example from the pull request could be something like this:

     let questionLabel = UILabel()
         ..textAlignment = .Center
         ..font = UIFont(name: "DnealianManuscript", size: 72)
         ..text = questionText

The expression could still work on a mutable struct/class which later becomes
immutable by using the `let` assignment.

The other example which silently creates a new instance is even worse.
If you want to do something like this, then please do it more explicitly.
E.g.:

     let fewerFoos = foos.clone()
         ..remove(at: i)

Anyway, all of this is simply syntactic sugar and should wait...

···

Am 2016-12-19 20:44, schrieb Erica Sadun via swift-evolution:

https://github.com/apple/swift-evolution/pull/346

--
Martin


(Miguel Bejar) #10

+1 on this. Scala also has a similar feature (copy constructor) for its
case classes. Right now there's no generic way to do this in Swift, besides
resorting to code generation.

Would this feature have an impact on the ABI and therefore be considered
for Swift 4 part 1?

-Miguel

···

On Mon, Dec 19, 2016 at 4:29 PM, Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

Thanks Erica, I wasn't aware of that proposal. If I'm reading it right,
the proposed 'with' function won't work for let-constants in structures,
e.g.:

struct Person {
    let name: String
    let address: String
}

@discardableResult
public func with<T>(_ item: T, update: (inout T) throws -> Void)
rethrows -> T {
    var this = item
    try update(&this)
    return this
}

let john = Person(name: "John", address: "1 battery st")
let jane: Person = with(john) { $0.name = "Jane" } // Cannot assign to
property: 'name' is a 'let' constant

Andy

On Dec 19, 2016, at 11:44 AM, Erica Sadun <erica@ericasadun.com> wrote:

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution < > swift-evolution@swift.org> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves
the immutability of the struct and doesn't require O(n^2) code for structs
with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic
initializer that does what your solution does. But that may be overkill
given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable
structs. I wasted too much time trying to solve this, I suspect others
would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com> wrote:

You are probably asking for a generic solution, but for a specific struct
you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String?
= nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution < > swift-evolution@swift.org>, wrote:

I like that structs are value types in Swift, this encourages the use of
immutable data. O'Caml has an operator "with" that allows for copying an
existing struct with a change to one field. I looked at Lenses for this
functionality and it seems like a lot to digest for something so simple. I
also tried to implement this using a constructor, or a function, and it was
not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this
syntax):

struct Person {
let name: String
let address: String
let phone: String
}

func f() {
let andy = Person(name: "Andy", address: "1 Battery St., San Francisco,
CA", phone: "1234567")
let chris = andy.with(name: "Chris")
let dave = andy.with(address: "50 Townsend St., San Francisco, CA")
}

I tried to implement a "with" function like this but default arguments
cannot reference properties of self. Same problem trying to do this in a
constructor.

Obviously it's possible to create an entirely new Person specifying the
values from an existing Person, but this is very tedious with structures
with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________
swift-evolution mailing list
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

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


(Derrick Ho) #11

That is correct Andy. Let-constant can not be assigned a new value after it
gets its initial value.

It is unclear why you are against turning your properties into var's.
Because you are using structs, all properties are copied by value.

struct Person {
  var name: String
}

let andy = Person(name: "Andy")
var brandon = andy; brandon.name = "Brandon"

andy.name // "Andy"
brandon.name // "Brandon"

I believe this accomplishes the same thing you wanted in with(name:)

···

On Mon, Dec 19, 2016 at 1:26 PM Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

Thanks Erica, I wasn't aware of that Swift evolution proposal. If I'm
reading it right, this wouldn't work with structs with let-variables...?
Here's what I get with this example:

struct Person {
    let name: String
    let address: String
}

@discardableResult
public func with<T>(_ item: T, update:
(inout T) throws -> Void) rethrows -> T {
    var this = item
    try update(&this)
    return this
}

let john = Person(name: "John", address: "1 battery st")
let jane: Person = with(john) { $0.name = "Jane" } // Test.swift:24:41:
Cannot assign to property: 'name' is a 'let' constant

Andy

On Dec 19, 2016, at 11:44 AM, Erica Sadun <erica@ericasadun.com> wrote:

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves the
immutability of the struct and doesn't require O(n^2) code for structs with
large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic
initializer that does what your solution does. But that may be overkill
given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable
structs. I wasted too much time trying to solve this, I suspect others
would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com> wrote:

You are probably asking for a generic solution, but for a specific struct
you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String? =
nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution < swift-evolution@swift.org>, wrote:

I like that structs are value types in Swift, this encourages the use of
immutable data. O'Caml has an operator "with" that allows for copying an
existing struct with a change to one field. I looked at Lenses for this
functionality and it seems like a lot to digest for something so simple. I
also tried to implement this using a constructor, or a function, and it was
not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this
syntax):

struct Person {

let name: String

let address: String

let phone: String

}

func f() {

let andy = Person(name: "Andy", address: "1 Battery St., San Francisco,
CA", phone: "1234567")

let chris = andy.with(name: "Chris")

let dave = andy.with(address: "50 Townsend St., San Francisco, CA")

}

I tried to implement a "with" function like this but default arguments
cannot reference properties of self. Same problem trying to do this in a
constructor.

Obviously it's possible to create an entirely new Person specifying the
values from an existing Person, but this is very tedious with structures
with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________

swift-evolution mailing list

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

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #12

Cascading falls under a separate proposal. There's an intermediate form which stagnated here: https://gist.github.com/erica/6794d48d917e2084d6ed

As Xiaodi Wu pointed out, you can introduce an implementation of `with` that uses reflection but that would be deferred to a later date when reflection itself gets updated.

-- E

···

On Dec 20, 2016, at 7:44 AM, Martin Waitz <tali@admingilde.org> wrote:

Am 2016-12-19 20:44, schrieb Erica Sadun via swift-evolution:

https://github.com/apple/swift-evolution/pull/346

-1
I don't like where this is heading.

If you want to introduce method cascading, then have a look at Dart.

E.g. the example from the pull request could be something like this:

   let questionLabel = UILabel()
       ..textAlignment = .Center
       ..font = UIFont(name: "DnealianManuscript", size: 72)
       ..text = questionText

The expression could still work on a mutable struct/class which later becomes
immutable by using the `let` assignment.

The other example which silently creates a new instance is even worse.
If you want to do something like this, then please do it more explicitly.
E.g.:

   let fewerFoos = foos.clone()
       ..remove(at: i)

Anyway, all of this is simply syntactic sugar and should wait...

--
Martin


(Derrick Ho) #13

Jeremy,

The problem you present is not a mutability problem but rather a cache
design problem. If your hash value truly is expensive then you only want to
calculated it when you need to...

struct Person: Hashable
{
    var firstName: String {didSet { _hashValue = nil }}
    var lastName: String {didSet { _hashValue = nil }}
    private var _hashValue: Int?
    var hashValue: Int {
        if _hashValue == nil {
            _hashValue = firstName ^ lastName // the "expensive hash
calculation"
        }
        return _hashValue!
    }
}

In the above implementation the hash value would only calculate the hash
when firstName or lastName were changed.

However in your example your hash method would calculate a new one every
time you copy a Person, but mine would not.

https://github.com/apple/swift-evolution/pull/346

-1

I don't like where this is heading.

If you want to introduce method cascading, then have a look at Dart.

E.g. the example from the pull request could be something like this:

     let questionLabel = UILabel()

         ..textAlignment = .Center

         ..font = UIFont(name: "DnealianManuscript", size: 72)

         ..text = questionText

The expression could still work on a mutable struct/class which later

becomes

immutable by using the `let` assignment.

The other example which silently creates a new instance is even worse.

If you want to do something like this, then please do it more

explicitly.

E.g.:

     let fewerFoos = foos.clone()

         ..remove(at: i)

Anyway, all of this is simply syntactic sugar and should wait...

···

On Tue, Dec 20, 2016 at 6:44 AM Martin Waitz via swift-evolution < swift-evolution@swift.org> wrote:
Am 2016-12-19 20:44, schrieb Erica Sadun via swift-evolution:

--

Martin

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution


(Andy Chou) #14

Value semantics help reduce the issues around mutability, but they don't go away completely. I would like to create structs that are completely immutable after construction. Turning the properties into vars unfortunately loses this idea.

The proposed 'with' function doesn't construct new instances, which means the let constants are already set. Nick's solution works, as it's basically a copy constructor that allows for changes while the new object is constructed. But it needs to be created for each struct. Which I'm fine with :slight_smile:

Andy

···

On Dec 19, 2016, at 1:47 PM, Derrick Ho <wh1pch81n@gmail.com> wrote:

That is correct Andy. Let-constant can not be assigned a new value after it gets its initial value.

It is unclear why you are against turning your properties into var's. Because you are using structs, all properties are copied by value.

struct Person {
  var name: String
}

let andy = Person(name: "Andy")
var brandon = andy; brandon.name <http://brandon.name/> = "Brandon"

andy.name <http://andy.name/> // "Andy"
brandon.name <http://brandon.name/> // "Brandon"

I believe this accomplishes the same thing you wanted in with(name:)

On Mon, Dec 19, 2016 at 1:26 PM Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Thanks Erica, I wasn't aware of that Swift evolution proposal. If I'm reading it right, this wouldn't work with structs with let-variables...? Here's what I get with this example:

struct Person {
    let name: String
    let address: String
}

@discardableResult
public func with<T>(_ item: T, update: (inout T) throws -> Void) rethrows -> T {
    var this = item
    try update(&this)
    return this
}

let john = Person(name: "John", address: "1 battery st")
let jane: Person = with(john) { $0.name <http://0.name/> = "Jane" } // Test.swift:24:41: Cannot assign to property: 'name' is a 'let' constant

Andy

On Dec 19, 2016, at 11:44 AM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves the immutability of the struct and doesn't require O(n^2) code for structs with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic initializer that does what your solution does. But that may be overkill given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable structs. I wasted too much time trying to solve this, I suspect others would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com <mailto:nick.keets@gmail.com>> wrote:

You are probably asking for a generic solution, but for a specific struct you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String? = nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>, wrote:

I like that structs are value types in Swift, this encourages the use of immutable data. O'Caml has an operator "with" that allows for copying an existing struct with a change to one field. I looked at Lenses for this functionality and it seems like a lot to digest for something so simple. I also tried to implement this using a constructor, or a function, and it was not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this syntax):

struct Person {

let name: String

let address: String

let phone: String

}

func f() {

let andy = Person(name: "Andy", address: "1 Battery St., San Francisco, CA", phone: "1234567")

let chris = andy.with(name: "Chris")

let dave = andy.with(address: "50 Townsend St., San Francisco, CA")

}

I tried to implement a "with" function like this but default arguments cannot reference properties of self. Same problem trying to do this in a constructor.

Obviously it's possible to create an entirely new Person specifying the values from an existing Person, but this is very tedious with structures with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________

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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org <mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #15

More advanced mirroring is one of those things that is on the agenda
somewhere. It would stand to reason that a generalizable solution which
doesn't need to be created for each struct would become possible when that
arrives, no?

···

On Mon, Dec 19, 2016 at 16:08 Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

Value semantics help reduce the issues around mutability, but they don't
go away completely. I would like to create structs that are completely
immutable after construction. Turning the properties into vars
unfortunately loses this idea.

The proposed 'with' function doesn't construct new instances, which means
the let constants are already set. Nick's solution works, as it's basically
a copy constructor that allows for changes while the new object is
constructed. But it needs to be created for each struct. Which I'm fine
with :slight_smile:

Andy

On Dec 19, 2016, at 1:47 PM, Derrick Ho <wh1pch81n@gmail.com> wrote:

That is correct Andy. Let-constant can not be assigned a new value after
it gets its initial value.

It is unclear why you are against turning your properties into var's.
Because you are using structs, all properties are copied by value.

struct Person {
  var name: String
}

let andy = Person(name: "Andy")
var brandon = andy; brandon.name = "Brandon"

andy.name // "Andy"
brandon.name // "Brandon"

I believe this accomplishes the same thing you wanted in with(name:)

On Mon, Dec 19, 2016 at 1:26 PM Andy Chou via swift-evolution < > swift-evolution@swift.org> wrote:

Thanks Erica, I wasn't aware of that Swift evolution proposal. If I'm
reading it right, this wouldn't work with structs with let-variables...?
Here's what I get with this example:

struct Person {
    let name: String
    let address: String
}

@discardableResult
public func with<T>(_ item: T, update:
(inout T) throws -> Void) rethrows -> T {
    var this = item
    try update(&this)
    return this
}

let john = Person(name: "John", address: "1 battery st")
let jane: Person = with(john) { $0.name = "Jane" } //
Test.swift:24:41: Cannot assign to property: 'name' is a 'let' constant

Andy

On Dec 19, 2016, at 11:44 AM, Erica Sadun <erica@ericasadun.com> wrote:

https://github.com/apple/swift-evolution/pull/346

Be aware that there's a bug that's being worked on:

https://bugs.swift.org/browse/SR-2773

-- E

On Dec 19, 2016, at 12:40 PM, Andy Chou via swift-evolution < > swift-evolution@swift.org> wrote:

Of course. Thanks for pointing out the obvious solution. This preserves
the immutability of the struct and doesn't require O(n^2) code for structs
with large numbers of fields.

I was thinking of a generic solution - perhaps something like a synthetic
initializer that does what your solution does. But that may be overkill
given how relatively easy it is to do this per struct...

On the other hand a generic solution would encourage using immutable
structs. I wasted too much time trying to solve this, I suspect others
would just give up and use var, or even classes.

Andy

On Dec 19, 2016, at 10:43 AM, Nick Keets <nick.keets@gmail.com> wrote:

You are probably asking for a generic solution, but for a specific struct
you can implement it like this:

extension Person {
    func with(name: String? = nil, address: String? = nil, phone: String?
= nil) -> Person {
        let name = name ?? self.name
        let address = address ?? self.address
        let phone = phone ?? self.phone
        return Person(name: name, address: address, phone: phone)
    }
}

On 19 Dec 2016, 20:28 +0200, Andy Chou via swift-evolution < > swift-evolution@swift.org>, wrote:

I like that structs are value types in Swift, this encourages the use of
immutable data. O'Caml has an operator "with" that allows for copying an
existing struct with a change to one field. I looked at Lenses for this
functionality and it seems like a lot to digest for something so simple. I
also tried to implement this using a constructor, or a function, and it was
not obvious how to do so without a lot of code duplication.

What's I'm looking for is something like this (not necessarily with this
syntax):

struct Person {

let name: String

let address: String

let phone: String

}

func f() {

let andy = Person(name: "Andy", address: "1 Battery St., San Francisco,
CA", phone: "1234567")

let chris = andy.with(name: "Chris")

let dave = andy.with(address: "50 Townsend St., San Francisco, CA")

}

I tried to implement a "with" function like this but default arguments
cannot reference properties of self. Same problem trying to do this in a
constructor.

Obviously it's possible to create an entirely new Person specifying the
values from an existing Person, but this is very tedious with structures
with many properties.

Anyone taken a look at this before? Any suggestions?

Andy

_______________________________________________

swift-evolution mailing list

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

_______________________________________________

swift-evolution mailing list

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


(Pierre Monod-Broca) #16

But for a struct to be immutable, you don't need all its properties to be let. (I guess that's Derrick's point)

´´´
struct Person { /* . . . var properties . . . */ }
let john = Person() // john is completely immutable
´´´

If a struct has var properties, you can do a mutable copy, change it, assign it to a new let, and you take advantage of immutability again.

´´´
let jim = { var copy = john
  copy.firstname = "Jim"
  return copy
}()

func with<T>(_ original: T, transform: (inout T) -> ()) -> T {
  var copy = original
  transform(&copy)
  return copy
}

let jane = with(john) { $0.firstname = "Jane" }
// jane is also immutable
´´´
(I didn't check this compiles)

Pierre

···

Le 19 déc. 2016 à 23:08, Andy Chou via swift-evolution <swift-evolution@swift.org> a écrit :

Value semantics help reduce the issues around mutability, but they don't go away completely. I would like to create structs that are completely immutable after construction. Turning the properties into vars unfortunately loses this idea.

The proposed 'with' function doesn't construct new instances, which means the let constants are already set. Nick's solution works, as it's basically a copy constructor that allows for changes while the new object is constructed. But it needs to be created for each struct. Which I'm fine with :slight_smile:

Andy


(Daniel Leping) #17

Ok. I'm positive with some sort of case classes.

+1 here

···

On Tue, 20 Dec 2016 at 23:02 Derrick Ho via swift-evolution < swift-evolution@swift.org> wrote:

Jeremy,

The problem you present is not a mutability problem but rather a cache
design problem. If your hash value truly is expensive then you only want to
calculated it when you need to...

struct Person: Hashable
{
    var firstName: String {didSet { _hashValue = nil }}
    var lastName: String {didSet { _hashValue = nil }}
    private var _hashValue: Int?
    var hashValue: Int {
        if _hashValue == nil {
            _hashValue = firstName ^ lastName // the "expensive hash
calculation"
        }
        return _hashValue!
    }
}

In the above implementation the hash value would only calculate the hash
when firstName or lastName were changed.

However in your example your hash method would calculate a new one every
time you copy a Person, but mine would not.

On Tue, Dec 20, 2016 at 6:44 AM Martin Waitz via swift-evolution < > swift-evolution@swift.org> wrote:

Am 2016-12-19 20:44, schrieb Erica Sadun via swift-evolution:

> https://github.com/apple/swift-evolution/pull/346

-1

I don't like where this is heading.

If you want to introduce method cascading, then have a look at Dart.

E.g. the example from the pull request could be something like this:

     let questionLabel = UILabel()

         ..textAlignment = .Center

         ..font = UIFont(name: "DnealianManuscript", size: 72)

         ..text = questionText

The expression could still work on a mutable struct/class which later

becomes

immutable by using the `let` assignment.

The other example which silently creates a new instance is even worse.

If you want to do something like this, then please do it more

explicitly.

E.g.:

     let fewerFoos = foos.clone()

         ..remove(at: i)

Anyway, all of this is simply syntactic sugar and should wait...

--

Martin

_______________________________________________

swift-evolution mailing list

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


(Jeremy Pereira) #18

Jeremy,

The problem you present is not a mutability problem but rather a cache design problem. If your hash value truly is expensive then you only want to calculated it when you need to...

struct Person: Hashable
{
    var firstName: String {didSet { _hashValue = nil }}
    var lastName: String {didSet { _hashValue = nil }}
    private var _hashValue: Int?
    var hashValue: Int {
        if _hashValue == nil {
            _hashValue = firstName ^ lastName // the "expensive hash calculation"
        }
        return _hashValue!
    }
}

In the above implementation the hash value would only calculate the hash when firstName or lastName were changed.

However in your example your hash method would calculate a new one every time you copy a Person,

No it wouldn’t. Copying a struct does not run the initialiser again.

So your solution is more complex and has more lines of code than mine. It is a good solution if your hash depends on properties that are truly meant to be mutable but if the properties are designed to be immutable, it is better to make them so and not have to introduce machinery in case they change unexpectedly.

···

On 20 Dec 2016, at 17:32, Derrick Ho via swift-evolution <swift-evolution@swift.org> wrote:

but mine would not.

On Tue, Dec 20, 2016 at 6:44 AM Martin Waitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 2016-12-19 20:44, schrieb Erica Sadun via swift-evolution:

> https://github.com/apple/swift-evolution/pull/346

-1

I don't like where this is heading.

If you want to introduce method cascading, then have a look at Dart.

E.g. the example from the pull request could be something like this:

     let questionLabel = UILabel()

         ..textAlignment = .Center

         ..font = UIFont(name: "DnealianManuscript", size: 72)

         ..text = questionText

The expression could still work on a mutable struct/class which later

becomes

immutable by using the `let` assignment.

The other example which silently creates a new instance is even worse.

If you want to do something like this, then please do it more

explicitly.

E.g.:

     let fewerFoos = foos.clone()

         ..remove(at: i)

Anyway, all of this is simply syntactic sugar and should wait...

--

Martin

_______________________________________________

swift-evolution mailing list

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


(Jeremy Pereira) #19

But for a struct to be immutable, you don't need all its properties to be let. (I guess that's Derrick's point)

Yes you do. Consider

struct Person: Hashable
{
    let firstName: String
    let lastName: String
    let hashValue: Int

    init(firstName: String, lastName: String)
    {
        self.firstName = firstName
        self.lastName = lastName
        self.hashValue = firstName.hashValue ^ lastName.hashValue
    }
}

func == (l: Person, r: Person) -> Bool
{
    return l.firstName == r.firstName && l.lastName == r.lastName
}

Pretend that the hash value is quite expensive to calculate so I only want to do it once. With the above code, this is fine but if I change the lets to vars (e.g. var firstName: String), I am open to

let chris = Person(firstName: “Chris”, lastName: “Lattner”) // Immutable

var andy = chris
andy.firstName = “Andy”

andy.hashValue // Gives the wrong answer unless you are exceptionally lucky.

I’ve used hashValue, but the same argument would apply to any computed property where you might want to cache the computed value. As soon as you make any of the properties that the computed property depends on `var`, you have to add code that invalidates the cached value which is a performance and a complexity hit for your struct.

···

On 20 Dec 2016, at 07:54, Pierre Monod-Broca via swift-evolution <swift-evolution@swift.org> wrote:

´´´
struct Person { /* . . . var properties . . . */ }
let john = Person() // john is completely immutable
´´´

If a struct has var properties, you can do a mutable copy, change it, assign it to a new let, and you take advantage of immutability again.

´´´
let jim = { var copy = john
copy.firstname = "Jim"
return copy
}()

func with<T>(_ original: T, transform: (inout T) -> ()) -> T {
var copy = original
transform(&copy)
return copy
}

let jane = with(john) { $0.firstname = "Jane" }
// jane is also immutable
´´´
(I didn't check this compiles)

Pierre

Le 19 déc. 2016 à 23:08, Andy Chou via swift-evolution <swift-evolution@swift.org> a écrit :

Value semantics help reduce the issues around mutability, but they don't go away completely. I would like to create structs that are completely immutable after construction. Turning the properties into vars unfortunately loses this idea.

The proposed 'with' function doesn't construct new instances, which means the let constants are already set. Nick's solution works, as it's basically a copy constructor that allows for changes while the new object is constructed. But it needs to be created for each struct. Which I'm fine with :slight_smile:

Andy

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


(Matthew Johnson) #20

But for a struct to be immutable, you don't need all its properties to be let. (I guess that's Derrick's point)

Yes you do. Consider

struct Person: Hashable
{
   let firstName: String
   let lastName: String
   let hashValue: Int

   init(firstName: String, lastName: String)
   {
       self.firstName = firstName
       self.lastName = lastName
       self.hashValue = firstName.hashValue ^ lastName.hashValue
   }
}

func == (l: Person, r: Person) -> Bool
{
   return l.firstName == r.firstName && l.lastName == r.lastName
}

Pretend that the hash value is quite expensive to calculate so I only want to do it once. With the above code, this is fine but if I change the lets to vars (e.g. var firstName: String), I am open to

let chris = Person(firstName: “Chris”, lastName: “Lattner”) // Immutable

var andy = chris
andy.firstName = “Andy”

andy.hashValue // Gives the wrong answer unless you are exceptionally lucky.

I’ve used hashValue, but the same argument would apply to any computed property where you might want to cache the computed value. As soon as you make any of the properties that the computed property depends on `var`, you have to add code that invalidates the cached value which is a performance and a complexity hit for your struct.

The performance hit is likely a bit larger if you *don't* use a mutable property and instead create a whole new instance.

You are right that the need to recompute derived data adds complexity. 'didSet' helps with that, but is still nontrivial if you were to have several derived values that depend on several properties each.

It might be interesting to think about language solutions to reduce this complexity. But in general, the mutability model of Swift's value types is an asset and should be embraced, not avoided. That's what a "Swifty" solution would do IMO.

···

Sent from my iPad

On Dec 20, 2016, at 4:32 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 20 Dec 2016, at 07:54, Pierre Monod-Broca via swift-evolution <swift-evolution@swift.org> wrote:

´´´
struct Person { /* . . . var properties . . . */ }
let john = Person() // john is completely immutable
´´´

If a struct has var properties, you can do a mutable copy, change it, assign it to a new let, and you take advantage of immutability again.

´´´
let jim = { var copy = john
copy.firstname = "Jim"
return copy
}()

func with<T>(_ original: T, transform: (inout T) -> ()) -> T {
var copy = original
transform(&copy)
return copy
}

let jane = with(john) { $0.firstname = "Jane" }
// jane is also immutable
´´´
(I didn't check this compiles)

Pierre

Le 19 déc. 2016 à 23:08, Andy Chou via swift-evolution <swift-evolution@swift.org> a écrit :

Value semantics help reduce the issues around mutability, but they don't go away completely. I would like to create structs that are completely immutable after construction. Turning the properties into vars unfortunately loses this idea.

The proposed 'with' function doesn't construct new instances, which means the let constants are already set. Nick's solution works, as it's basically a copy constructor that allows for changes while the new object is constructed. But it needs to be created for each struct. Which I'm fine with :slight_smile:

Andy

_______________________________________________
swift-evolution mailing list
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