[swift-evolution] "with" operator a la O'Caml?

Matthew Johnson matthew at anandabits.com
Wed Dec 21 08:07:09 CST 2016


> On Dec 21, 2016, at 4:34 AM, Jeremy Pereira <jeremy.j.pereira at googlemail.com> wrote:
> 
>> 
>> On 20 Dec 2016, at 13:10, Matthew Johnson <matthew at anandabits.com> wrote:
>> 
>> 
>> 
>> Sent from my iPad
>> 
>>> On Dec 20, 2016, at 4:32 AM, Jeremy Pereira via swift-evolution <swift-evolution at swift.org> wrote:
>>> 
>>> 
>>>> On 20 Dec 2016, at 07:54, Pierre Monod-Broca via swift-evolution <swift-evolution at swift.org> wrote:
>>>> 
>>>> 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.
> 
> How is 
> 
>    let a = SomeStruct()
>    var b = a
> 
> not creating a new instance?

Of course this creates a new instance.  But it’s not what I was talking about.  What I was distinguishing is the performance characteristics of pure functional code that simply modifies one or more properties like this:

let x = Person(firstName: “John”, lastName: “Black”)

// don’t pay attention to syntax - any syntax with the same semantics 
// would have the same performance characteristics
let y = Person(firstName: x.firstName, lastName: “White”)

Relative to code that leverages the mutability model of Swift’s value types:

var x = Person(firstName: “John”, lastName: “Black”)
x.lastName = “White"

The former is usually going to be slower because it requires copying, additional storage, and re-runs the entire initializer despite only modifying a single property.  Sometimes the optimizer may be able to eliminate the difference, but that is not always going to be the case.

Your observation that a lot of boilerplate is required if you need to cache a derived property is very fair.  That is a legitimate problem that would be reasonable to solve.

> 
> Anyway, the cost depends on how expensive the calculation for the calculated property is and how often you use it and how well the compiler can optimise copies of immutable objects.
> 
> On the other hand, making a property that is not supposed to change over the lifetime of the object a let property is self documenting and not to be avoided IMO.

This is perfectly fair.  But if you need the ability to construct a nearly identical instance with only one, or maybe a few, modified properties you probably have code that would benefit from using Swift’s model for mutability of value types.

> 
>> 
>> 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.
> 
> Yeah, I really hate it when people say “x is Swifty” or “y is not Swifty”. What is Swifty or not usually depends on what the person saying it prefers. On the other hand, most programmers i have come across agree that writing code that is self documenting is good practice and therefore using let instead of var for properties that never change over the life time of the object counts in that respect in my opinion.

“Swifty” was followed by IMO so it was clearly a statement of my personal opinion.  I agree that self documenting code is a great practice.  

On the other hand there isn’t an increase in understanding of the semantics of a struct property by using  `let` instead of `var` if that property will often be modified via an initializer that effectively creates a copy of a previous instance, but with a new value for said property.  The differences between `let` and `var` in this case are pretty subtle and not usually consequential.

There are certainly times when using let rather than var for members of a struct makes sense.  An identifier is a great example where let is usually appropriate.  But in *my* opinion there is a widespread misunderstanding of the semantic difference between let and var for structs.

To demonstrate, in the following example both implementations of `updateOwner` have identical semantics:

class Foo {
    var owner: Person
    
    // pure functional style
    func updateOwner(newFirstName: String) {
        owner = Person(firstName: newFirstName, lastName: owner.lastName)
    }

    // using Swift’s model for value type mutation, obviously only works if `firstName` is declared with `var`
    func updateOwner(newFirstName: String) {
        owner.firstName = newFirstName
    }
}

If your type is intended to be used in code with semantics like this there is no significant benefit to the pure functional style.  Today there is a price in terms of syntax which could be solved provided sufficient motivation.  There is also a price in terms of performance that may sometimes be optimized away, but not always.

I’m not necessarily suggesting that one style or the other should always be preferred.  What I am suggesting is that we should be clear about understanding what the tradeoffs are.  And I am also suggesting that there are enough benefits to the mutability model for value types that identifying challenges with writing code that relies on this model (such as caching a computed property) is a more fruitful avenue for improving the language than offering syntactic sugar for more functional, perhaps lens-y style code.  That’s not to say we can’t do both, but we do need to prioritize.

> 
> 
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161221/96c04e2c/attachment.html>


More information about the swift-evolution mailing list