I've linked to the pitch here.
Do people want this in the standard library? This is my first pitch.
I've linked to the pitch here.
Do people want this in the standard library? This is my first pitch.
I'm not sure what would be the best name, but I have a similar function called modify
in many of my projects. One important use case you're not mentioning is modifying the value at the other end of subscript or some other kind of chained expression:
var dict = ["someKey", [1, 2, 3]]
modify(&dict["someKey"]) { value in
switch value?.count {
case 2...: value!.removeLast()
default: value = nil // remove entry for empty array
}
}
Here you can resolve the chain to the subscript only once while making a change to its value. Otherwise you'd have to write this:
var dict = ["someKey", [1, 2, 3]]
switch dict["someKey"]?.count {
case 2...: dict["someKey"]!.removeLast()
default: dict["someKey"] = nil // remove entry for empty array
}
Edit: On second reading, I realize my modify
function isn't the same thing as what is proposed here. You are returning a modified copy of the value whereas my function takes the value inout
and changes it in place. I guess they are different things, although a bit related similar to how reduce(_:_:)
and reduce(into:_:)
are related.
That could be another with
overload.
@inlinable
@discardableResult
public func with<T>(_ value: inout T, transform: (inout T) throws -> Void) rethrows -> T
@inlinable
@discardableResult
public func with<T>(_ value: inout T, transform: (inout T) async throws -> Void) async rethrows -> T
+1 from me. Personally I’m a big fan of with
; it helps structure code that has to do lots of setup (e.g. code working with Metal and setting up render passes), and in general I like the prospect of leaning into the functional programming side of swift a bit more (although I don’t know if everyone does).
The other main benefit I like is that it allows the variable itself to be immutable, which makes sense for a variable of that nature (and makes it clear that it gets setup once and then left alone, which isn’t always clear otherwise and I’ve had a bug caused by something like that).
I think the name with
is good because that’s used in other languages/libraries already, and we already have the precedent of withUnsafePointer
, withContinutation
, etc
No strong opinions on the name or if this is something worth including, but I too have written this exact helper function and found it useful.
I've wanted something like this for a while, but a more correct implementation would be:
@inlinable
func with<T>(_ object: consuming T, update: (inout T) throws -> ()) rethrows -> T {
try update(&object)
return consume object // I'm not sure that this consume is required.
}
For the same reason that parameters to initializers default to consuming.
I too have this exact function (with the same name) in my own personal library. It would be great to have it in the standard library.
+1. Previous discussions:
I think this is one of these cases, where a lot of people come up with similar extensions in their projects to fulfill a similar need, so its probably worth thinking about if this warrants a language level solution.
In terms of alternatives to with
here is a solution that similar to the one @anon9791410 proposed uses a dynamicMemberLookup
, callAsFunction
and a postfix operator /
to achieve a syntax like this:
// Given
struct Starship {
var commandingOfficer: String
var registry: String
var isActive: Bool
}
extension Starship {
static let titan = Starship(
commandingOfficer: "William T. Riker",
registry: "NCC-80102",
isActive: false
)
}
// Application Example
struct TVShow {
let titanRefit = Starship.titan/
.commandingOfficer("Liam Shaw")/
.registry("NCC-80102-A")/
.isActive(true)
}
// or
struct TVShow {
let titanRefit = Starship.titan/ {
$0.commandingOfficer = "Liam Shaw"
$0.registry = "NCC-80102-A"
$0.isActive = true
}
}
+1
For me, this is a long wanted feature. I really like Dart's cascade operator - the ergonomics/expressiveness of writing and using it lead to me prefer that spelling.
Regarding the naming, if we have an inout
variant I think it should actually have another name. And to me what would more closely follow the naming guidelines is something like this:
// modify in place
modify(&value) { $0.x = 2 }
// returns modified value
let newValue = modifying(value) { $0.x = 2 }
Overloading with
to mean both of these would be confusing I think.
Well, in both method variants you're doing something with
the value, but they do have different semantics.
While that looks pretty cool in practice, I feel like the syntax is non-obvious, and it's less flexible; you can't call methods on the value or access variables with throws
or async
getters.
For us this concept is extremely useful. The way we actually implement it though is with the following operator:
precedencegroup FunctionApplicationPrecedence {
associativity: left
higherThan: BitwiseShiftPrecedence
}
infix operator &>: FunctionApplicationPrecedence
public func &> <Input>(
value: Input,
function: (inout Input) throws -> Void
) rethrows -> Input {
var m_value = value
try function(&m_value)
return m_value
}
The operator is spelled &>
because it represents the "inout" version (thus the &
) of the common "pipe" operator |>
(which we also use).
The usage of this operator has greatly improved our code and made us more productive, even in unexpected ways. For example, if we need to only append some values to an array if a condition is true
we write something like this:
return [1, 2, 3] &> {
if shouldInclude4 {
$0.append(4)
}
}
I would definitely support any proposal that added both |>
and &>
to the standard library. I'm not a fan of the with
free function though, because it requires writing with
before the value that's going to be mutated. Also, I don't like the extra parentheses. In absence of scope functions à-la Kotlin, I think an operator is a better solution.
I'll add these operators to Alternatives Considered
.
Interesting: your inout
version is about whether the closure takes an inout
parameter or not. But my inout
version is about whether the value is passed inout
or not. We have a 2×2 grid of inout
possibilities there.
An alternative syntax that feels potentially more idiomatic could be to use result-builder-style / SwiftUI-style method chaining:
// Instead of using a closure:
let components = with(URLComponents()) { components in
components.path = "foo"
components.password = "bar"
}
// We could use method chaining:
let components = URLComponents()
.path("foo")
.password("bar")
It would certainly be nice if there was a way to support this pattern without having to manually implement the method for each corresponding property. I suppose a macro could be created for this, but it would be really nice in my opinion if this were possible on all types without having to modify the source of the type to adopt an annotation or anything.
The operator is designed to specifically return something that should be passed to the next expression (usually a returned value, or a value passed as argument to a function), hence the >
part: if it's only about mutating an existing value with inout
, I think the operator should look like an assignment, so something like
func &= <Input>(
value: inout Input,
function: (inout Input) throws -> Void
) rethrows {
try function(&value)
}
The function isn't really useful if it doesn't take an inout
parameter; what would you do with that?
If this was possible, Kotlin-style generic scope functions would be the best solution, in my opinion.