"Apply" is the word used in describing an application of a behavior to data in APL and derived languages, which don't use ANY symbol or keyword to get do this.
I wouldn't say it's implied... For all of mutate/modify/update, I could also imagine a method that modifies the variable, not the elements of a collection. That said, I'm not against it .
Here's the list of suggestions so far:
mapInPlace
formMap
forEach (unfortunately, it's already taken)
mutateAll
mutate(all:)
mutateEach
withEach
applyInPlace
apply
mapped / formMapped
modifyEach / updateEach
transform
mutate(with:)
modify
update
adjust
toEach
My favorite is forEach (and at the same time deprecating the existing forEach), but that seems like a no-go (breaking people's code and/or making type-inference more complex).
Also, so far, support seems to be in support of the idea (I also like the implementation improvement by Nevin that manually advances the index). I would also be interested in arguments against adding it.
By the way, I don't think the in-place version without an inout closure provides enough functionality compared to the version with inout, or compared to map. Phrased in popular Swift Evolution speak: "it doesn't pull it's own weight".
I find the applyInPlace Erica suggested most appropriate.
Though I wonder, this function seems to be part of a recent family that adopts a previously purely functional pattern to Swiftâs idiosyncratic inout. Are we working around the compilerâs lack of optimizations for functional programming with extensions on API level? Arenât we also precluding their introduction by opening this Pandoraâs box of impurity?
I think Swift fully embraces mutation, and because of copy-on-write and value semantics it has many of the same benefits as the "pure" variants. So if you consider mutation impure, and part of Pandora's box, it might be good to run away from Swift while you still can ;). That said, in an unrelated note, of course it'd be super awesome if the non-mutating and mutating variants could be optimized to have similar performance, but that's out of scope for my proposal.
Would you agree that we seem to be hand-rolling inout âoptimizationsâ for functional patterns?
I mean the compiler properly armed with the copy-on-write, value semantics and uniquely referenced things should be able to do these in-place mutating optimizations for us. At least thatâs my hand waving understanding of how optimizing compilers for advanced functional languages like Haskell work. But that relies on certain guarantees stemming from pure functions. Once we swap them for side-effects via inout mutations, all that flies out the window.
I fully understand and support the pragmatic nature of these API extensions. Iâm just pointing out that it should probably give us a pause. By peace-wise scratching these individual itches, weâll pretty much guarantee there will never be enough pressure to do these functional optimization in the compiler.
One of my favorite things about Swift is that the functional and imperative parts live in harmony next to each other. I like that we, the programmers, can choose between the two. This proposal gives you the choice between map and a mutability variant. To me, it's not meant as an optimization: it's a different way to write things. The fact that it has more predictable performance is a nice side-effect.
With regard to compiler improvements: it's not so easy to actually optimize functional code: if you look at the Haskell code for optimized programs (for example here) using advanced compilers, you'll see that most of it is written in a style that's very imperative, and uses a lot of mutability, and even pointers.
By the way, don't get me wrong: functional programming is really great, and more people should look into it. I just think that Swift's mutation model is also really great (and more people should look into it?).
It seems to me that very often we want to have both a mutating and non-mutating version of functions/methods. Imho this leads to much boilerplate as this is usually implemented as making the mutating one of the two implement the logic, and the other by making a copy and performing the mutation on that. Would it maybe make more sense to focus on a language-supported way to easily switch between the two forms and have the need for only one (the mutating one) defined in code? There is also an ongoing pitch (the with one) for doing just this.
Though even doing that won't really solve the issue when the implementation is defined as non-mutating, as I don't see an easy way to convert that into a mutating one...
I wouldn't mind if Swift had a better story for mutating vs. non-mutating variants, but in this case, the two are different: map can change the type of the elements, whereas the in-place version can't. So we couldn't derive the immutable variant from the mutable variant. Both are independently useful.
For example, map always gives you back an array. If you're working with, let's say, a large UnsafeMutableBufferPointer, it's not so nice to have to map, construct an intermediate array, and then loop over the resulting array to modify each element in place. I cannot imagine a compiler that could somehow optimize that, although I'm happy to be proven wrong!
True, but since already-defined functors are all value types, the mutable one could be defined based on the immutable one, e.g. self = self.map(transform). This gets harder when considering also possible reference types implementing this, though...
For what I understand, this might be more of a byproduct of the lack of Higher Kinded Types support in Swift. Some selected similar functions do keep the type though, e.g. Set.filter.
If what you're proposing could somehow be derived automatically, the argument to map would then (magically?) need to change from (A) -> B into (inout A) -> (). I'm sorry for pitching it with the name mapInPlace, it's really a different beast.
I realised now how different the pitched functionality is, sorry for that but what benefit does the pitched form give over one that would mutate self and not mutate the elements but replace them with the result of the given transform (which would still be (A) -> B)?
Sorry, I didn't specify that transform would still be (A) -> B, but on the mutating case B would be constrained to be == A.
More concretely I was asking what benefit would mapInPlace(_ transform: (inout A) -> Void) give opposed to mapInPlace(_ transform: (A) -> A)
Since this thread still seems to be going, @chris_eidhof, should Swift be treating these mutable items as @discardable_result and returning self for further chaining/assignment?
It's worth noting that, with more of the ownership model fleshed out, map itself could get most of the in-place-modification optimization benefit from consuming its self argument and having the map closure consume its argument as well. This would allow a map of a uniquely-referenced value to reuse the value's buffer, possibly even while changing the type, as long as the buffer is large enough to hold the resulting array.