Good point. It's really a mutating forEach, isn't it?
No, it is is only settled for words which are “naturally described by a noun.”
The API guidelines are currently ambiguous as to whether that means “words that may function as nouns” or “words that must function as nouns to avoid awkwardness.” I argue, based on the swift-evo discussion that lead to that guideline, that the latter interpretation is the correct one.
If the former interpretation were correct, then we would also use the method names formPartition
, formUpdate
, formSubtraction
, formMerge
, and arguably formSort
, formInsertion
, and even formSuffix
instead of dropFirst
, since all those words can function as nouns.
The ridiculousness of all those names suggests the “may” reading of that convention is not the right one.
Again, recall that the “form-” convention was only invented to address the awkwardness of names like unioningInPlace
.
Yes. Consider:
- In ML-family languages, which IMO half-includes Swift, a “map” can refer to a curried call to map which already has its predicate and accepts a collection.
- In some languages, “map” as a noun is a synonym for “dictionary.”
Or more simply, just compare the naming alternatives I posted above with the eyes of somebody who didn’t follow the discussion that lead to formIntersection
.
It’s really a mutating forEach, isn’t it?
I have implemented this in the past, under the name mutateAll
.
This was my first thought.
This is similar to the discussion about reduce(into:)
(Reduce with inout - #26 by Joe_Groff) - you could argue for another function also called forEach
with an inout
parameter, but that would probably be too much stress on the type-checker.
I wonder if we could do something like:
extension MutableCollection {
func forEach(_ perform: (inout Element)->Void)) { ... }
@available(*, unavailable)
func forEach(_ perform: (Element)->Void)) { ... }
}
Since we're moving in the direction of providing both mutating and non-mutating versions of the core Sequence/Collection methods, do you think it's worth including multiple in one proposal?
filterInPlace
has already been brought up and seems like a natural parallel to the proposed method—I suspect that if one is supported, the other likely would be too.
It's worth mentioning the in-place equivalents for the other common functional API as well:
- a
flatten
method that performs the effect ofself = flatMap { $0 }
in place (can be implemented today as an extensionwhere Element: Sequence
) - a
compact
(?) method that performs the effect ofself = compactMap { $0 }
in place (cannot be implemented without parameterized extensions or an additional protocol forOptional
as far as I can tell)
extension MutableCollection { mutating func mapInPlace(_ x: (inout Element) -> ()) { for i in indices { x(&self[i]) } } }
This implementation falls into a performance trap, as described in the documentation for indices
. Essentially, indices
might hold a reference to self
, in which case mutating self
while iterating over indices
creates an extra copy of self
. In the standard library, this occurs for Dictionary.Values
.
The recommended approach is to manually advance an index.
extension MutableCollection {
mutating func mutateAll(_ f: (inout Element) throws -> ()) rethrows {
var i = startIndex
while i != endIndex {
try f(&self[i])
formIndex(after: &i)
}
}
}
Also, if you want to directly pass in a function (eg. sin
) rather than a closure, then the parameter should not be inout
:
extension MutableCollection {
mutating func mutateAll(_ f: (Element) throws -> Element) rethrows {
var i = startIndex
while i != endIndex {
self[i] = try f(self[i])
formIndex(after: &i)
}
}
}
The difference is in how you call it:
x.mutateAll{ $0 *= $0 } // inout
x.mutateAll{ $0 * $0 } // non-inout
x.mutateAll(sin) // non-inout
I‘d prefer mutateEach
instead to kind of mimic the naming of forEach
which is the non-mutating part of it.
No, it is is only settled for words which are “naturally described by a noun.”
The API guidelines are currently ambiguous as to whether that means “words that may function as nouns” or “words that must function as nouns to avoid awkwardness.” I argue, based on the swift-evo discussion that lead to that guideline, that the latter interpretation is the correct one.
If the former interpretation were correct, then we would also use the method names formPartition, formUpdate, formSubtraction, formMerge, and arguably formSort, formInsertion, and even formSuffix instead of dropFirst, since all those words can function as nouns.
The ridiculousness of all those names suggests the “may” reading of that convention is not the right one.
Again, sounds like the underlying motivation is that you just don't like names with form
and think they're "ridiculous."
There is no need to parse the wording so finely. The API guideline write-up leaves much to be desired because gerunds are nouns in the first place. Simply, there are two and only two conventions for mutating vs. non-mutating method names: the "verb vs. noun" (ed/ing) convention, and the "form" convention when names can't use the first convention. When it comes to map
, it's an exception to the "verb vs. noun" (ed/ing) convention, so the next one is the "form" convention.
Yes. Consider:
In ML-family languages, which IMO half-includes Swift, a “map” can refer to a curried call to map which already has its predicate and accepts a collection.
In some languages, “map” as a noun is a synonym for “dictionary.”
Do you really think that Swift users are going to see formMap
and wonder whether it's "a curried call to map which already has its predicate" or a synonym for a dictionary? Or, again, do you just not like names with "form"?
The difference is in how you call it:
x.mutateAll{ $0 *= $0 } // inout x.mutateAll{ $0 * $0 } // non-inout x.mutateAll(sin) // non-inout
The last two examples are truly just map
; not having to assign to self
is a pretty meager win. The reason that an in-place version is so desirable is precisely to enable the inout
functionality used in the first example, and I'd argue that's the only one we should be enabling.
@DevAndArtist's suggestion mutateEach
sounds pretty good.
I‘d prefer mutateEach instead to kind of mimic the naming of forEach which is the non-mutating part of it.
Me too, or: withEach
.
I agree that the method is too far from map
(since it's only "mapping" back onto itself, with the same element type) for it to be named mapInPlace
or formMap
.
When it comes to map, it’s an exception to the “verb vs. noun” (ed/ing) convention, so the next one is the “form” convention.
If we are reading the guidelines clause by clause, that is not the rule the guidelines actually state. They draw the distinction in terms of words that are more naturally nouns.
However, this legalistic nit-picking over the correct way to dutifully follow the letter of the guidelines completely misses the substance of my argument, which is:
-
the considerations that originally lead to the name
formUnion
do not apply here, and -
formFilter
andformMap
fail to meet the first of the “Fundamentals” in the API design guidelines, namely clarity at the point of use, regardless of what particular rules say. We should interpret the particular API guidelines laws in a manner consistent with the API guidelines constitution, as it were.
Do you really think that Swift users are going to see formMap and wonder whether it’s “a curried call to map which already has its predicate” or a synonym for a dictionary?
I really think Swift users seeing formMap
or formFilter
would be mightily confused. I have no idea what they would think. You asked for examples of other possible interpretations, and I provided them.
Again, sounds like the underlying motivation is that you just don’t like names with form and think they’re “ridiculous.”
I don’t like these particular names with form
, and would invite others in this discussion beside Xiaodi to weigh in whether they think they pass the smell test.
Again, the choice is:
filterInPlace
mapInPlace
*
…versus:
formFilter
formMap
* (unless we do a forEach variant, as others propose)
Others, what do you think of formFilter
and formMap
? Are they clear at the point of use? Fluent? Natural?
Others, what do you think of formFilter and formMap? Are they clear at the point of use? Fluent? Natural?
I don't think either makes much sense and don't think it makes sense to argue for either. formFilter
does not describe the action being performed, and neither does formMap
.
I'm OK avoiding this argument entirely, though, in favor of a mutating forEach
or another name, since we're losing the (A) -> B
freedom of map
.
I don’t like these particular names with
form
, and would invite others in this discussion beside Xiaodi to weigh in whether they think they pass the smell test.
It's neither here nor there given that we have a consensus that the best name for the proposed function is something that doesn't have anything to do with map
, but I'm genuinely curious about this: you're saying everything but confirming or denying whether you dislike the form
convention in general.
So let me ask you this directly: Are you saying that you like other particular names with form
? Or do you dislike them all?
I really think Swift users seeing
formMap
orformFilter
would be mightily confused. I have no idea what they would think. You asked for examples of other possible interpretations, and I provided them.
No, I did not ask for "possible" interpretations, I asked whether it was "a realistic point of confusion"; it sounds like you don't believe that to be the case.
but I’m genuinely curious about this: you’re saying everything but confirming or denying whether you dislike the form convention in general.
So let me ask you this directly: Are you saying that you like other particular names with form? Or do you dislike them all?
I don't think this is the place for this conversation. Let's keep it focused on the pitch. If either of you is interested in revisiting the form
prefix, let's take it to another thread.
No, I did not ask for “possible” interpretations, I asked whether it was “a realistic point of confusion”; it sounds like you don’t believe that to be the case.
In the specific case of defining it as formMap
here, I think that it's totally a realistic point of confusion in that it doesn't make a whole lot of sense to me semantically.
I don’t think this is the place for this conversation. Let’s keep it focused on the pitch. If either of you is interested in revisiting the
form
prefix, let’s take it to another thread.
No, I'm not at all interested in revisiting the form
prefix, but I am very interested in understanding where people are coming from when they approach a design problem such as this.
It's hard to engage in meaningful conversation when someone deliberately mentions another aim ("+1 to the InPlace
suffix...this remains the clearest, best option"/"the entire stdlib sorely needs an audit for consistent availability of mutating and non-mutating counterparts"/"contra extending the confusing, easily misread formFoo
convention") and then refuses to actually confirm that it's what they're doing.
So I'm calling it out, and the only place to do that is when and where it happens.
No, I’m not at all interested in revisiting the form prefix, but I am very interested in understanding where people are coming from when they approach a design problem such as this.
I still think you could solicit this feedback in a new thread or private message. This is different enough from the focus.
I'm a big +1 on the concept/functionality.
If we're going to bike shed on the name, then:
I dislike using the word form
as part of the API name, because "form" is just another term for "make", implying that formMap
means I'm creating a map, which is not the case. Ditto formFilter
: I'm not making a filter, I'm applying a filter.
I like the "obviousness" of the name mapInPlace
although I agree with @Chris_Eidhof that this technically isn't a map, unless you're willing to down the road of saying that a (T) → T
is a map (which wouldn't be a hard sell).
I don't really like mutateInPlace
because IMO mutate
is an uncommon enough word in English that it can increase the barrier to entry for non-english speakers. However, given that we already use mutate
elsewhere in our API, I wouldn't object strongly to using this term in the name.
TL;DR:
+1 on the functionality
-1 on formMap
+½ on mapInPlace
or mutateInPlace
I dislike using the word form as part of the API name, because “form” is just another term for “make”, implying that formMap means I’m creating a map, which is not the case. Ditto formFilter: I’m not making a filter, I’m applying a filter.
If this is the potential confusion to be avoided, then that issue can be easily accommodated with a slight adjustment to formMapped
.
I like the idea. It seems to promise something along the lines of space efficiency rather than generating a sequence or returning a same-type copy. I'd prefer mutateInPlace
or applyInPlace
for f(_:T) -> T
.
(I considered apply
but that might seem to people to be too similar to map
and indicate a non-mutating application)
The use of mapped
in this form would be extremely unconventional. The only usage of mapped
in Foundation comes from the NSData
methods that deal with mapped memory.
formMapped
would get an even stronger -1 from me.