SE-0293: Extend Property Wrappers to Function and Closure Parameters

A agree with the rest of the post, but there are a few important pieces:

I'm sorry. I could not at all parse this sentence, even though it seems to be directly addressing my concern. Could you elaborate more on that?

Sorry if I'm not clear, my "incongruence" means that you cannot exactly mimic the generated initializer with the provided feature. If we want to simplify the mental model, we need to make it possible to match the behaviour of the generated initializer with this feature.

Let's be careful here. The choice of [pass by wrapper] and [pass by wrapped] is within the control of the author of the wrapper (but not of the lib author or the lib user). It is an important distinction since of the three parties, we want to give most of the controls of the call-site to the lib and the wrapper authors. This is one of Swift's guiding principles.


@hborla It does seem we're talking in a circle, and sometimes passing each other. If you'd kindly humour me for a moment, there are a few scenarios I would like your opinions on. Which among them are expected, and which are desirable? They of course can (and should) overlap. It'd also be great if you can elaborate on those that you deem desirable since I don't think they're ever desirable at any level of programming expertise and/or library authorship. So here goes;

  1. We get different function types when implementing memberwise initializer with this feature:

    struct Defaulted {
      @Binding var a: Int
      @State var b: Int
    }
    struct New {
      @Binding var a: Int
      @State var b: Int
    
      init(@Binding a: Int, @State b: Int) {
        self._a = _a
        self._b = _b
      }
    }
    
    let def = Defaulted.init(a:b:) // (Binding<Int>, Int) -> Defaulted
    let new = New.init(a:b:) // (Binding<Int>, State<Int>) -> New
    
  2. If we use @Lowercased with ForEach, the array must contain Lowercased instead of String

    // array must be `[Lowercased]` instead of `[String]`
    ForEach(array) { (@Lowercased a) in
    }
    
  3. Binding works with ForEach example, but won't work with normal function scenario (which are most of the Lowercased examples)

    // OK
    ForEach(array) { (@Binding a) in
    }
    
    func foo(@Binding a: ...) -> Content { ... }
    
    // Somehow OK because `foo` is of type `(Binding) -> ()`
    ForEach(array, content: foo)
    
    // Can't call `foo` because `Binding` doesn't have `init(wrappedValue)`
    foo(...) //error
    
  4. The lib user can use either pass-by-wrapper or pass-by-wrapped. The lib and the wrapper authors do not have any means to control that:

    func foo(@Lowercased a: String) { ... }
    
    // User can pass a `String`
    foo("Test")
    
    // But can also be sneaky and directly pass a `Lowercased`
    let bar = foo
    foo(Lowercased("Test"))
    
  5. The unapplied method reference doesn't match the single-expression closure

    func foo(@Lowercased a: String) { ... }
    
    // `bar1` and `bar2` have different type
    let bar1: (Lowercased) -> () = foo
    let bar2: (String) -> () = { foo($0) }
    

If none of these are desirable, then why are we paying for these? What is so desirable about the current behaviour that we can tolerate these (potentially common) surprises?

5 Likes