I'll try to officially propose this since it seems like there's some interest.

2 Likes

Hi @aggie33,

As noted above, this has been pitched a number of times before—it might even have been reviewed once but I can’t recall off the top of my head. Be sure to study those threads and incorporate those insights as you refine your pitch/proposal: it’s valuable feedback that contributors have taken the time and effort to share already.

2 Likes

I couldn't find anything like this in the pitches folder of Swift Evolution, but maybe it didn't make it that far? I'll look again.

Pitches are posted in these forums; you can use the “search” function. There are also links from this thread already posted which you can follow. This idea has been discussed at great length so you should be able to find at least several threads totaling hundreds of posts of commentary.

I did search for it but I couldn't find anything about it. Could you link to the thread?

+1 I had to do manual functions for this purpose very often. I used this approach a lot for building stub models in unit tests.

@S2Ler has already linked it above:

Was that ever officially pitched?

Pitches aren't really official in any sense. One could maybe say that they are official when there is a pitch thread in Evolution/Pitches, which there is in this case.

This pitch didn't reach the phase of a proposal review though, which is when it actually becomes official.

That's what I meant. I guess this is a revival of that pitch then.

I, too, have created this functionality. It should be in the standard lib, but I’m not at all into the with spelling. It’s just too generic and doesn’t tell me at all what we’re attempting to do.

Some suggestions would be mutated, or modified, but I’m not married to those either.

1 Like

mutated and modified both suggest that the instance is being mutated or modified, and I think this functionality would be most useful if it could be used both with structs with, and more-so without, mutating methods.

Here it would match the with behaviour you get in C# on immutable records, which I really like.

This looks very much like the Builder Pattern:

extension URLComponents: Buildable {}

let components = URLComponents.builder()
    .path("foo")
    .password("bar")
    .build()

The Builder pattern is based on the power of Protocols, Dynamic member lookup and Keypaths:

@dynamicMemberLookup
class Builder<T> {
    private var value: T
    
    init(_ value: T) { self.value = value }
    
    subscript<U>(dynamicMember keyPath: WritableKeyPath<T, U>) -> (U) -> Builder<T> {
        {
            self.value[keyPath: keyPath] = $0
            return self
        }
    }
    
    func build() -> T { self.value }
}

protocol Buildable { init() }

extension Buildable {
    static func builder() -> Builder<Self> {
        Builder(Self())
    }
}
5 Likes

Someone else in this thread had a similar idea using an operator instead of a method. However this has some limitations compared to a with function. You can't call methods on the value you're mutating; you have to extend each type you want to do this with the Buildable protocol; and the use of key-paths creates some overhead compared to just directly mutating the property.

I proposed the operator, which solves all the issues of the method and doesn't require keypaths. It's seems from your post that that when you say

you are referring to the method, right? The operator has none of such issues.

I’ve used a similar pattern before and the naming convention that made the most sense was mutate.

func mutate<T>(_ value: inout T, with transform: (T) throws -> Void) rethrows -> T
1 Like

I'm not the biggest fan of the name. It doesn't really reflect "why" I'd use that function. It also clashes in my mind with "with" keyword from python that serves a totally different purpose.

I'm more in favour of some keyword that would go in the direction of a "builder", maybe even a macro.

Yes, the operator would allow you to perform arbitrary modification of the value, and it doesn't require key-paths. However, should we add a new operator to the standard library for this one function? It could result in source-break if someone else defined such an operator. Which operator would be best to use?

You proposed using the &> operator, which kind of looks like an arrow.
I think you could also use |> or -->. I prefer --> if an operator were to be used, since it looks more like an arrow in my opinion.

|> has a different meaning in the state of art, that is

public func |> <Input, Output>(
  value: Input,
  function: (Input) throws -> Output
) rethrows -> Output {
  try function(value)
}

so, basically, function application without inout.

I don't have a strong opinion about the actual symbol to use (I think &> is very nice precisely because it looks like |>, which is state of art and used by other languages, with the added & that suggests inout mutation, but also &|> would be fine to me in that sense), we can bikeshed it.

However, to this question

I think the answer is 100% yes: the operator would likely be used frequently, for a real and diffuse need, so it definitely reaches the bar for addition to the standard library. In terms of possible code breakage, I think if someone already defined it in their code base, their definition would take precedence. AFAIK we cannot disambiguate operators, but we cannot disambiguate methods either, and actually even a new free function could produce source breakage (in fact, I think it's more likely, because defining new operators is rarer in Swift).

For many years I tried several different alternatives, and my team and I are pretty positive that an operator is, overall, the best solution, save for Kotlin-like scope functions (automatically available for all values), but those would require compiler work.

4 Likes

I agree, personally I think operator shapes are purely conventional, the "way they look" is completely subjective to me. For example, the only reason why = is "assignment" is really because in a certain family of languages, conventionally and traditionally, = has been used as assignment.

I consider the pipe operator |> state of art, and it's used extensively in functional languages like F# and Elixir, and I think the shape comes from the fact that | is the pipe operator in Unix, and the > simply suggest a flow to the right. Thus, if we introduced this concept officially in Swift, I'd be hard pressed to come up with reasons to not use |> for that purpose.