[swift-evolution] mutating/non-mutating suggestion from a Rubyist
Thorsten Seitz
tseitz42 at icloud.com
Tue Apr 26 08:48:57 CDT 2016
> Am 23.04.2016 um 10:27 schrieb Pyry Jahkola via swift-evolution <swift-evolution at swift.org>:
>
> 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.
I think I like that idea.
> 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)
> }
>
> 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.
That doesn't look so bad (we might shorten 'mutate' to 'mut', though I don't think that would be really necessary).
I'm wondering how to deal with fluent interfaces which do mutate the receiver and return 'self', i.e. the builder pattern. I think we can simply require that such a chained expression is required to consist of only mutating calls (or only non-mutating calls), so that one 'mutate' for the whole expression would be sufficient.
Expressions combining mutating calls to different receivers which have return values would simply be prohibited. This would probably be bad style anyway (contrary to the fluent example), or does anyone have a good counter example?
> 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? 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 really very straightforward.
As an alternative to the mutate keyword I rather like using '&' because it already means 'inout'. Using '&.' as mutating method call would therefore be quite understandable.
c&.frobnicate(i)
let k = c&.frobnicate(&i)
Or marking the method like suggested elsewhere:
c.frobnicate&(i)
let k = c.frobnicate&(&i)
-Thorsten
>
>> 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.
>
> — Pyry
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160426/fcc1f397/attachment.html>
More information about the swift-evolution
mailing list