Opposite of Optional<T>.take() to set to .some value iff nil

mutating func take() -> Wrapped? is neat. I wonder what's a good name for an opposite operation, one that sets it to .some value iff it's .none?

The desired effect would result in transforming this:

self.myOptionalValue = self.myOptionalValue ?? someDefaultValue

to this:

self.myOptionalValue.___(someDefaultValue)

(where ___ is the method's name)

Like an "emplace" or "insert if empty" or "replace if none", but maybe there's a more fitting name.

3 Likes

What about:
fill (you cannot fill something which is already filled)
plug (alternative to fill, you cannot plug something which is not empty)

3 Likes

How about a ??= operator?

3 Likes

My immediate thought was “put” since it feels like a mild antonym of “take”.

self.myOptionalValue.put(someDefaultValue)

I suppose that’s about as clear as “take” in expressing its function (not that clear).

1 Like

Do not reassign if equal; that is not the same thing as assigning if nil. This test passes:

var myOptionalValue: Int? = 0 {
  didSet {
    if myOptionalValue == 0 { myOptionalValue = 2 }
  }
}

let someDefaultValue = 1

#expect(myOptionalValue == 0)
myOptionalValue = myOptionalValue ?? someDefaultValue
#expect(myOptionalValue == 2)

myOptionalValue = nil
if myOptionalValue == nil { myOptionalValue = someDefaultValue }
#expect(myOptionalValue == someDefaultValue)

What you're asking for should be this, or very similar:

extension Optional where Wrapped: Sendable {
  struct SomeError: Error { let value: Wrapped }

  mutating func reassignNil(to some: Wrapped) throws(SomeError) {
    switch self {
    case .none: self = some
    case .some(let value): throw .init(value: value)
    }
  }
}

If you don't care about the error, just use try?.

try? myOptionalValue.reassignNil(to: someDefaultValue)

It's even very simple to implement:

infix operator ??= : AssignmentPrecedence

extension Optional where Wrapped: ~Copyable {
  @inlinable
  public static func ??= (lhs: inout Self, rhs: @autoclosure () -> Wrapped) {
    lhs = switch consume lhs {
    case .none: .some(rhs())
    case .some(let wrapped): .some(wrapped)
    }
  }
}
2 Likes

How about topup(...). A bit more fun than fill I think :slight_smile:

Topup entails adding to already existing content.

Which has the wrong connotation, since an Optional cannot be partially filled.

In fact we want it to have the connotation that it is empty. Why plug is better than fill.

Agreed, but in any case, my problem with all the suggestions above is that none of them convey conditionality. You don't just fill, you fill if. The ??= operator in this sense probably comes the closest to what would be the most intuitive way of performing this operation.

1 Like

Yeah, but plug of peg does convey it, cannot be done if something is in the hole already (read: Optional)

But it has the obvious downside: not all Optional.Wrapped == Peg.

To peg an Optional<Bool> sounds weird.

Unfortunate, because I cannot come up with another verb which conveys both:
”The container (Optional) cannot be partially filled” && ”This action does nothing if something already occupies the container”

Intuitively, I reached for the operator, too (Ruby has ||= for example).

Like @calicoding points out, "take" isn't that good in the first place, so maybe a nameless operator is the better choice anyway.

This was SE-0024: Optional Value Setter ??=.
It was rejected for the standard library nine years ago, but you can always add it for your own use!

IIUC, Optional.take() doesn't mean only taking the value but also it means taking the ownership of the value, since it was (re-)introduced as a part of SE-0437 "Noncopyable Standard Library Primitives".
I think the function opposite to take() would be also supposed to take over the ownership of the new value. Copyable value may be passed as well though.
In short, such a function name could be a verb where both "value" and "ownership" can be its object word.

...Although I'm pointing that out, I'm so poor at English that I don't come up with any words for this.:sweat_smile:
accept(_:)? obtain(_:)? receive(_:)?
devolve(_:)? give(_:)? offer(_:)? transfer(_:)?
(Phew... they don't imply "if it's nil" at all.)

By the way, whatever the name would be, I may want the function to return the ownership of the passed value if some wrapped value already exists:

extension Optional where Wrapped: ~Copyable {
  @discardableResult
  public mutating func _nameOppositeToTake_(_ newValue: consuming Wrapped) -> Wrapped? {
    switch consume self {
    case .some(let alreadyWrapped):
      self = .some(alreadyWrapped)
      return newValue
    case .none:
      self = .some(newValue)
      return nil
    }
  }
}

I don't think a ??= operator is worse, but any operators can't achieve such behavior.

1 Like

That would be spelled:

extension Optional where Wrapped: ~Copyable {
  mutating func exchange(with newValue: consuming Wrapped) -> Self {
    let old = consume self
    self = newValue
    return old
  }
}

Which is a completely different operation.

1 Like

Optional.take() was apparently defined before SE-0429 landed. SE-0429’s motivating example is a swap() function that works on a Pair of non-copyable values. We can logically extend this to Optional, in which case assignment is simply discarding the result of swap(): _ = myOptional.swap(newValue).

That said, there have been some other ideas floating around that might render Optional.take() superfluous:

  1. In-place mutation of enum payloads
  2. Referencing enum payloads as storage locations
// Imagining `var` pattern bindings as the syntax for (1):
if case myOptional = .some(var wrapped) {
  wrapped = newValue
}

// Imagining a syntax for (2):
myOptional.some.0 = newValue

Sorry for derailing...

My implementation is to return input as output if self is not nil. That's not exchange:


I admit I was too circumstantial in a roundabout manner.
I just wanted to say we'd better to consider copy-ability to name the function.