[swift-evolution] mutating/non-mutating suggestion from a Rubyist

Dave Abrahams dabrahams at apple.com
Mon Apr 25 13:48:35 CDT 2016


on Sat Apr 23 2016, Pyry Jahkola <pyry.jahkola-AT-iki.fi> wrote:

> I'd like to second James Campbell's suggestion of a `mutate` keyword. Clarifying
> comments inline below:
>
>     On 23 Apr 2016, at 00:24, Dave Abrahams via swift-evolution
>     <swift-evolution at swift.org> wrote:
>
>     This is not a new idea. Something almost identical to this has been
>     explored and discussed quite thoroughly already:
>     <https://github.com/apple/swift/blob/master/docs/proposals/Inplace.rst>.
>     In fact, it was implmented and later reverted because it raised
>     language-design questions for which we had no good answers.
>
> I don't know if the following are particularly good answers, but I'll try
> anyway:
>
>     I don't believe the choice of glyph (& vs =) affects any of the
>
>     fundamental issues:
>
>     * Should the x.=f() syntax be required for *every* mutating method
>     invocation?
>
> Allow me to ask it differently: Should some specific syntax be required for
> every mutating method? — Yes.
>
> Should the syntax be `x.=f()`? — Not necessarily. I kinda like James Campbell's
> idea of a `mutate` keyword. Consider the following:
>
> var numbers = [5, 12, 6, 2]
> mutate numbers.append(10)
> mutate numbers.sort()
> if let biggest = mutate numbers.popLast() {
> print("The biggest number was:", biggest)
> }

Not that syntax is the most important question here, but that syntax is
super-heavyweight by comparison and unlikely to fly with many people
(including me) for that reason.

> So `mutate` would work much like `try` but—unlike `try` which can move
> further to the left—`mutate` would have to always prefix the mutating
> receiver. Here's a contrived example of a corner case:
>
> enum Error : ErrorType { case BadNumber }
>
> func demo() throws -> Int {
> }
>
>     * Are assignment methods a redundant way to spell mutating methods?
>     Should we really have both mechanisms?
>
> (I had to look up the definition of an assignment method. For the uninitiated,
> Dave is talking about what's written here:
> https://github.com/apple/swift/blob/master/docs/proposals/Inplace.rst#use-one-simple-name.)
>
> — Yes they are redundant, and no, we should not have both.
>
> With `mutate` required at the call site, we could simply allow both overloads
> `func sort()` and `mutating func sort()` to coexist, because the call sites
> become unambiguous:
>
> let originals = [2, 1, 3, 0, 4, 2]
> var copies = originals
>
> originals.sort() // warning: result of call to 'sort()' is unused
> mutate originals.sort() // compiler error
> let xs = originals.sort() // ok
>
> copies.sort() // warning: result of call to 'sort()' is unused
> mutate copies.sort() // ok
> let ys = copies.sort() // ok
> let zs = mutate copies.sort() // warning: constant 'x' inferred to have type '
> ()', which may be unexpected
>
> The language could also allow the use of
>
> mutate x.next()
>
> as shorthand for
>
> x = x.next()
>
> when only the non-mutating variant `func next() -> Self` exists with
> compatible return type.
>
>     * Can we really introduce this feature without having a way to apply it
>     to class types?
>
> Yes we can. Why complicate the naming of value type members with the
> complexities of reference semantics? 

Because some people still imagine that classes should have all the same
basic capabilities as value types, and that protocols should unify them.
That makes these people suspicious of language features that could
threaten to make classes “second-class citizens.”

Maybe the Foundation value types effort will begin to change that; I'm
not sure.

> The current API naming conventions are good for reference types which
> sometimes come with unobvious to obscure behaviour (i.e. anything from
> bumping an internal counter to firing missiles and wiping hard
> drives).
>
> But value types ought to have no side effects (besides memory allocation and
> logging maybe), and so we don't necessarily need that strong a naming convention
> to limit their collateral damage.
>
> If the `mutate` keyword became required for calling `mutating` methods, then
> operators would remain the only place where naming convention were needed to
> distinguish mutation:
>
> * Mutating assignment is explicit: `xs = [1, 2] + xs + [2, 1]` (i.e. `=` without
>   `let` or `var` means mutation)
> * Mutating method call becomes explicit: `mutate xs.sort()` and `let x = mutate
>   xs.removeAtIndex(2)`
> * Mutating function arguments are explicit with the `&` prefix: `swap(&xs, &ys)`
> * Mutating operators are implicit and by convention, should end with the `=`
>   symbol: `xs += [8, 9]`
> * Reference types have no notion of `mutating` members (and probably ought to
>   remain that way) so they mutate implicitly.
>
>     I should also point out that under the assignment method paradigm one
>     would probably need to re-evalutate rules for naming. Under the current
>     API guidelines' approach, we'd write:
>
>     x.=sorted() // sort x in-place
>
>     and I am not sure how easy that would be for people to swallow
>     considering how much more straightforward
>
>     x.sort() // current way to sort x in-place
>
>     is, and because the language now contains explicit notation for
>     mutation, it becomes harder to argue against theis pair:
>
>     y = x.sort()
>     x.=sort() // sort x in place
>
> I agree that the current API guidelines wouldn't work for value types anymore.
> Both `sort` and `sorted` would be called `sort`.
>
>     Lastly, I should point out that the proposal does nothing to solve the
>     problem of `c.formSuccessor(&i)`, since that doesn't mutate the
>     receiver.
>
> This proposal does address the problem of `c.formSuccessor(&i)`. Given that it's
> value types at play here, what mutates in the following is unambiguous even to
> non-native English speakers:
>
> c.frobnicate(&i) // cannot possibly mutate c but mutates i
> let j = c.frobnicate(i) // cannot possibly mutate either
> mutate c.frobnicate(i) // mutates c
> let k = mutate c.frobnicate(&i) // mutates both

That's one possible answer.  

>     I still like the proposal's basic approach and would love to see it used
>     to address these naming problems, but I want to be clear that it's by no
>     means a panacea and there are real obstacles between here and actually
>     being able to apply it. If you want to move forward with something like
>     this, you need to solve the problems described above.
>
> I think this proposal would simplify all code handling value types. Yes, it adds
> one keyword of boilerplate but wins clarity in return. I think reference types
> should stay separate from this discussion, as their mutation has always been
> implicit anyway. The API guidelines set a good convention for them.

Interesting.  When you say it, it all sounds so reasonable… yet, I was
making the same arguments when the feature was pulled out of Swift.
Maybe someone other than me needs to lay out the issues more clearly.

-- 
Dave


More information about the swift-evolution mailing list