Cant figure out mutation semantic

There is a weirdness happening in the code that makes me awake at night.

infix operator => : DefaultPrecedence
func => <T>(lhs: inout T, rhs: (inout T) -> ()) -> T {
   rhs(&lhs)
   return lhs
}
//now goes the mud
let a = Int(8) => {
    $0 += 123
}

The latter part does not compile with an error: the lvalue is not mutable. How would you fix that?

Is this a trick question? Int(8) is not mutable, so the error sounds exactly right.

Maybe you mean:

func => <T>(lhs: T, rhs: (inout T) -> ()) -> T {
   var l = lhs
   rhs(&l)
   return l
}
5 Likes

Huh, this is strange. I had an intuition that structs can be mutated if they are captured as inout argument. Is int not mutable because it is a special case or something?

let a = Int(8) => {
    $0 += 123
}

...is the same as...

let x = Int(8)
let a = x => {
    $0 += 123
}

x is not mutable, and for the same reason, neither is Int(8) when substituted directly.

You would need to explicitly create mutable storage:

var x = Int(8)
let a = x => {
    $0 += 123
}

(Of course that may still not express what you want its semantics to be. See @QuinceyMorrisโ€™s guess at what you probably want instead.)

3 Likes

Admin note: please put something descriptive in the title. "Uhmm, what?" gives no indication what the post is about. The forums are very active and it's polite to help participants decide which topics to read and which to skip.

10 Likes

Int(8) is just a value. It doesn't declare any storage. For there to be mutation, you must create storage to mutate, using var. It is the value in that storage that can be mutated when you pass it inout.

You might say, well, why doesn't the compiler implicitly create storage for me, since that's obviously what's needed to make this compile. But that would allow callers to accidentally discard a mutated value without realizing it. Presumably the argument is inout for a reason โ€“ the mutation it performs on the argument is meaningful. So you can think of the compiler not doing this as similar to it requiring you acknowledge a return value of a function even if you aren't interested in it e.g. with _ = f().

4 Likes

Seems to me like this is another use case for the ever-missing "box". :crying_cat_face:

I think ยง (option-6) is a cooler-looking emulation of the $ prefix, but you do you, until it gets standardized! :sunglasses:

postfix operator =>
postfix func => <Value>(value: Value) -> Wrapped<Value> {
  .init(value)
}
let a = 1=> { $0 + 1 } // 2

var b = a=>
b { $0 += 1 }
b { $0 += 1 }
b.wrappedValue // 4

You need two overloads.

/// An affordance for adding extensions to every type.
@propertyWrapper public struct Wrapped<Value> {
  public var wrappedValue: Value
  public var projectedValue: Self { self }

  public init(wrappedValue: Value) {
    self.wrappedValue = wrappedValue
  }
}

public extension Wrapped {
  init(_ wrappedValue: Value) {
    self.init(wrappedValue: wrappedValue)
  }

  func callAsFunction(
    transform: (Value) throws -> Value
  ) rethrows -> Value {
    try transform(wrappedValue)
  }

  mutating func callAsFunction(
    transform: (inout Value) throws -> Void
  ) rethrows {
    try transform(&wrappedValue)
  }
}
1 Like