JetForMe
(Rick M)
1
I’m having a very hard time googling for an answer to this issue I ran into. I wanted to create an optional assignment operator that would make expressions like this more compact:
foo = updatedFoo ?? foo
It looks like this:
protocol
OptionalAssignable : Any
{
static func ??=(lhs: inout Self, rhs: Self?)
}
infix operator ??= : AssignmentPrecedence
extension
OptionalAssignable
{
static
func
??=(lhs: inout Self, rhs: Self?)
{
if let v = rhs
{
lhs = v
}
}
}
extension Float : OptionalAssignable {}
extension Int : OptionalAssignable {}
extension String : OptionalAssignable {}
This works well, when the types are non-optional, and I can write some database update code that looks like this:
furnace?.number ??= furnaceUpdate.number
furnace?.name ??= furnaceUpdate.name
furnace?.minTemp ??= furnaceUpdate.minTemp
furnace?.maxTemp ??= furnaceUpdate.maxTemp
furnace?.idleTemp ??= furnaceUpdate.idleTemp
furnace?.gas1Name ??= furnaceUpdate.gas1Name
furnace?.gas2Name ??= furnaceUpdate.gas2Name
Some of those are Int, some are Float, but unfortunately, some are String?`, and the compiler gets mad at me for those, saying
referencing operator function '??=' on 'OptionalAssignable' requires that 'String?' conform to 'OptionalAssignable'
I had hoped it would just figure it out. I tried to make an overloaded ??= that took two Self? arguments, but then I get "member operator '??=' of protocol 'OptionalAssignable' must have at least one argument of type 'Self'."
I tried to solve it by adding a conformance for Optional, but that not only feels…unethical, it doesn’t work, as the operator definition ends up being ??=(lhs: inout Optional, rhs: Optional?), and the test fails.
Update:
Am I missing a better approach?
3 Likes
tem
(GalaxySwift)
2
I would make the operator a generic function like this:
infix operator ??= : AssignmentPrecedence
func
??=<T>(lhs: inout T, rhs: T?)
{
if let v = rhs
{
lhs = v
}
}
That solves the compiler error, but it still doesn't work as I'd expect:
// furnace.name is an Optional<String>
furnace.name ??= String?.some("Cooker")
furnace.name // "Cooker"
furnace.name ??= String?.none
furnace.name // nil
This is also true if you call a function that returns String?.none.
It seems like we could work around that by overloading the operator in an extension for Optional:
extension
Optional
{
static
func
??=(lhs: inout Self, rhs: Self)
{
if let v = rhs
{
lhs = v
}
}
}
Does that work for you?
djankolija
(Davor Jankolija)
3
Refining a little on what @tem suggested. Although not sure if this is what you're looking for and it's definitely not the most elegant solution:
infix operator ??=: AssignmentPrecedence
func ??=<T>(lhs: inout Optional<T>, rhs: Optional<T>) {
if lhs == nil {
lhs = rhs
} else if let rhs = rhs {
lhs = rhs
}
}
furnace?.name ??= "Blaster"
furnace?.name // Blaster
furnace?.name ??= "Burner"
furnace?.name // Burner
furnace?.name ??= nil
furnace?.name // Burner
2 Likes
To consolidate the ideas above, you need two overloads.
public extension Optional {
static func ??= (optional0: inout Self, optional1: Self) {
if let some = optional1 {
optional0 = some
}
}
static func ??= (wrapped: inout Wrapped, optional: Self) {
if let some = optional {
wrapped = some
}
}
}
3 Likes
JetForMe
(Rick M)
6
I did end up making the extension on Optional, and it does work. I suppose the generic function is better, as it relieves the user of the need to conform other types.
JetForMe
(Rick M)
7
What's the purpose of the Wrapped version? Is that in lieu of any other definitions?
Assigning to non-optionals.
tera
9
Wow, three different methods here...
- protocol
- generic function
- extending Optional
The last one looks best.
PS. I'd choose slightly nicer and shorter ?=
1 Like
tem
(GalaxySwift)
10
Cool. I do find @anon9791410's version easier to understand, and it makes sense to group everything in an extension on Optional, and it removes the need for angle brackets.
Neat idea by the way, I'm going to try to work this into my own projects.
But I think =? would be a better name because it reads more like "assign maybe", similar to calling an optional closure self.action?(), and ?= is more easily confused with ? = which already has a different meaning when the LHS is an optional.
2 Likes
JetForMe
(Rick M)
11
I chose ??= because it was similar to += and the like. Perhaps ?= is better, but the double-question mark was reminiscent of the nil-coalescing operator used in the expanded expression.
1 Like
Lantua
12
It does feel like x ??= y should be x = x ?? y though, not the other way around.
8 Likes
tem
(GalaxySwift)
13
Oh, right. Then I think either ??= or =?? or =? would be good, but I'd still avoid ?= because of the potential for confusion with ? = (also, what if you were the type of person to not put spaces around assignment operators, then what?).
=?? would not be as consistent with the compound assignment operators like += but given that this operator works a bit differently (as @Lantua mentions above), it wouldn't benefit much from that consistency anyway in my opinion.
Extra scientific rationale
=?? also has a shorter Hamming distance / Levenshtein distance compared to ??=
tera
14
What does ? = stand for? That's not valid swift or is it?
I see. you mean:
var x: Int? = xxx
x ?= xxx
I think that spelling is best too, because it's a more clear shorthand for the underlying switch statement.
static func =? (optional0: inout Self, optional1: Self) {
switch optional1 {
case let some?:
optional0 = some
case .none:
break
}
}
2 Likes
Ben_Cohen
(Ben Cohen)
16
This thread is intriguing to me because it hasn't come up that ??= is ambiguous. Is it
someValue = updatedValue ?? someValue
or
someValue = someValue ?? fallbackValue
What's interesting is the OP defines it the unconventional way around. This matters a lot because ?? isn't commutative. So it would be like defining x /= y as x = y / x. The use case presented makes sense, but if I encountered this operator in some code, it's not the use I would assume at first.
(I realize the goal of the post was not to debate this but to figure out how to define it... just a drive-by observation)
8 Likes
tera
17
A hypothetical new syntax if there were no limits whatsoever:
someValue = updatedValue ?? _
someValue = _ ?? fallbackValue
Here _ (bike-shedding) is used as "lhs placeholder".
JetForMe
(Rick M)
18
The =? spelling is growing on me, although I’m not sure it's enough to eclipse the ??= code I've already written.
Given the level of interest in this discussion, I wonder how widely used this type of assignment is (i.e. “only change this value if a new value was specified”). I had a handful of places in my code where I was doing this and it was a fun exercise, but not really needed.
I was wondering if I could make it even more compact (and possibly more useful), and I started pondering something like this:
let somethingChanged = conditionallyAssign(destination, source, \.$prop1, \.$prop2, \.$prop3)
I don’t know enough about key paths to know if something like that is even possible across different types. But the idea is, update the properties of destination with the values of the properties of source, and return true iff at least one value changed. This would let me avoid the DB write that happens after the update, in my application.
JetForMe
(Rick M)
19
I suppose ?= could do one, and =? could do the other. 
As to ?= being potentially confused with ? =, can it? Won’t the lexer treat them differently? I suppose it wouldn't be too hard to test it, but I haven’t.
tem
(GalaxySwift)
20
FWIW, JavaScript has something very similar where x ??= y has the meaning x = x ?? y (but without any assignment when x already has a value). It's called nullish coalescing assignment
1 Like