[swift-evolution] mutating/non-mutating suggestion from a Rubyist
Tyler Cloutier
cloutiertyler at aol.com
Wed Apr 27 19:44:03 CDT 2016
> On Apr 23, 2016, at 1:27 AM, Pyry Jahkola via swift-evolution <swift-evolution at swift.org> 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 <mailto: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 <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)
> }
>
> 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 <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
How would this chain if I wanted to do something like:
let median = foo.calculateBigHugeArray().sort().medianValue()
and I want the sort to be done in place. Or will this type of thing just be disallowed in favor of.
let array = foo.calculateBigHugeArray()
mutate array.sort()
let median = array.medianValue()
Alternately you could replace the method invocation operator with &
let median = foo.calculateBigHugeArray()&sort().medianValue()
Or depending how sacrilegious you’re feeling you could use the only character on the keyboard that isn’t currently used and doesn’t require the shift key and is easily distinguished from a ‘.’
let median = foo.calculateBigHugeArray()'sort().medianValue()
This is definitely more confusing than the & and mutate, since & is used to indicate mutation elsewhere.
Also, if you wanted to stick with consistent & syntax, you could do:
&c.frobnicate(i)
and
let k = &c.frobnicate(&i)
Dave, to your point about classes, there is currently already special syntax for value types with the & as parameters. Reference semantics is the wild west there anyway.
>
>> 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/20160427/413da0d7/attachment.html>
More information about the swift-evolution
mailing list