Swift-Native Alternative to KVO


(Jared Sinclair) #1

The one-to-many observer pattern could really use a first-party, native Swift solution. The day-to-day practice of writing iOS / OS X applications needs it, and we end up falling back on antiquated APIs like KVO or NSNotificationCenter, or short-lived third-party libraries. This is an essential need that deserves a fresh approach.

What follows is a rough proposal for a Swift-native “KVO alternative”.

What Usage Would Look Like:

let tweet = Tweet(text: “Hi.”)
tweet.observables.isLiked.addObserver { (oldValue, newValue) -> Void in
    // this is otherwise just a standard closure, with identical
    // memory management rules and variable scope semantics.
    print(“Value changed.”)
}
tweet.isLiked = true // Console would log “Value changed.”

Behind the Scenes:

- When compiling a Swift class “Foo", the compiler would also generate a companion “Foo_SwiftObservables” class.

- When initializing an instance of a Swift class, an instance of the companion “ Foo_SwiftObservables” class would be initialized and set as the value of a reserved member name like “observables”. This member would be implicit, like `self`.

- The auto-generated “ Foo_SwiftObservables” class would have a corresponding property for every observable property of the target class, with an identical name.

- Each property of the auto-generated “ Foo_SwiftObservables” class would be an instance of a generic `Observable<T>` class, where `T` would be assigned to the value of the associated property of the target class.

- The `Observable<T>` class would have two public functions: addObserver() and removeObserver().

- The addObserver() function would take a single closure argument. This closure would have a signature like: (oldValue: T?, newValue: T?) -> Void.

- Observer closures would have the same memory management and variable scope rules as any other closure. Callers would not be obligated to remove their observer closures. Doing so would be a non-mandatory best practice.

Rough Code Examples

Consider a class for a Twitter client like:

class Tweet {
    var isLiked: Bool = false
    let text: String
     
    init(text: String) {
        self.text = text
    }
}
The compiler would generate a companion observables class:

class Tweet_SwiftObservables {
    let isLiked = Observable<Bool>()
}
Notice that only the `isLiked` property is carried over, since the `text` property of `Tweet` is a let, not a var.

The generic Observable class would be something like (pseudo-codish):

class Observable<T> {
    typealias Observer = (oldValue: T?, newValue: T?) -> Void
    private var observers = [UInt: Observer]()
     
    func addObserver(observer: Observer) -> Uint {
        let token: Uint = 0 // generate some unique token
        self.observers[token] = observer
        return token
    }
     
    func removeObserverForToken(token: Uint) {
        self.observers[token] = nil
    }
}

Benefits of This Approach

It’s familiar. It resembles the core mechanic of KVO without the hassle. It uses existing memory management rules. Everything you already understand about closures applies here.

It’s type-safe. The Observable<T> generic class ensures at compile-time that your observers don’t receive an incorrect type.

It’s readable. The syntax is brief without being unclear. Implementing the observation closure at the same call site as addObserver() keeps cause and effect as close together as possible.

It’s easy. It abandons a stringly-typed API in favor of a compile-time API. Since the Foo_SwiftObservables classes would be auto-generated by the compiler, there’s no need for busywork tasks like keeping redundant manual protocols or keyword constants up to date with the target classes.

Thanks for reading,

···

--
Jared Sinclair
@jaredsinclair
jaredsinclair.com


(Charles Srstka) #2

I’ve been thinking about this for a while, as well. I think the problem would be better served by a simpler approach. Generating entire new classes is the way the existing KVO mechanism works, but I don’t really see the necessity in it.

Here’s my counter-pitch: add an “observable" keyword on property declarations, so your “isLIked” property would look like this:

class Tweet {
  observable var isLiked: Bool = false
  let text: String

  init(text: String) {
    self.text = text
  }
}

My first instinct is to make observations based on strings, as KVO does, as this is easier to integrate with XIB-based user interface elements, as well as making it possible for it to interact with the legacy KVO system. Using strings also makes it possible to bind to key paths, which can be useful. However, if one wanted to make the observations based on pointers rather than strings, for type safety, that would also be possible.

The strings would be provided by a parameter on the “observable” attribute (i.e. observable(“foo”)); if no parameter is provided, Swift would automatically insert the property name as the key.

When a property is marked “observable”, the Swift compiler rewrites the class to something resembling the following pseudocode:

class Tweet {
  var isLiked_Observers: [(oldValue: Bool, newValue: Bool) -> ()]? = nil

  var isLiked: Bool {
    didSet(oldValue) {
      // optional for performance reasons; in the common case where there are no observers,
      // checking an optional has less of a performance penalty than checking whether an array is empty.
      if let observers = self.isLiked_Observers {
        for eachObserver in observers {
          eachObserver(oldValue: oldValue, newValue: self.isLiked)
        }
      }
    }
  }
  let text: String

  init(text: String) {
    self.text = text
  }
}

If there are no observers, the only cost added to the setter would be that of setting an optional.

What usage would look like:

let tweet = Tweet(text: “Hi.”)

tweet.addObserverFor(“isLiked") { oldValue, newValue in
    print(“Value changed.”)
}

tweet.isLiked = true // Console would log “Value changed.”

If isLiked later becomes a calculated property, it would add a “depedencies” attribute, which would return a set of other observable properties, like so:

class Tweet {
  observable var numberOfLikes: Int = 0
  observable var isLiked: Bool {
    dependencies { return ["numberOfLikes"] }
    get { return !self.numberOfLikes.isEmpty }
    set(isLiked) {
      if isLiked {
        self.numberOfLikes += 1
      } else {
        self.numberOfLikes = 0 // or something; it’s just an example
      }
    }
  }
  let text: String

  init(text: String) {
    self.text = text
  }
}

In this example, the “isLiked” property would generate an observation on “numberOfLikes” that would look something like the following. For this example, we introduce a cached version of the previous value of “isLiked" so that we have a copy of the old value in the case that the dependency changes (and isLiked’s willSet and didSet thus won’t fire). An alternative solution would be to run observation closures before *and* after setting each property, as KVO does; however, this would not perform as well, and would be more difficult to manage in multithreaded situations.

// generated by the compiler and added to initialization
self.addObserverFor(“numberOfLikes”) { [weak self] _, _ in
  if let observers = self.observers {
    for eachObserver in observers {
      eachObserver(oldValue: isLiked_Cached, newValue: self.isLiked)
    }
  }

  isLiked_Cached = self.isLiked
}

This would cause our notifications to be fired even for computed properties when one of the dependencies changes.

One final little perk would allow us to specify a dispatch queue upon which observations should be fired:

class Tweet {
  observable var isLiked: Bool = false {
    dispatchQueue { return dispatch_get_main_queue() }
  }
  let text: String

  init(text: String) {
    self.text = text
  }
}

The benefits of this should not need explanation.

Benefits of this approach:

- It’s relatively simple and lightweight, with no additional classes being created, and the common case adding only the cost of an optional check to property setters.

- It’s easy to understand and to use.

- All observable properties are marked “observable” in the UI. As an example of why this is desirable, consider that you had been observing the “isLiked” property from the first example, and then the implementation of the Tweet class changed to the “numberOfLikes” implementation without the author considering KVO. Your observer code would break, and you would no longer get notifications for “isLiked” if “numberOfLikes” changed. Having an “observable” keyword communicates a contract to the user that this property will remain observable, and any changes will be made in a way that won’t break observability. Such contract is important to have if your client code is relying on the observation working properly.

- An additional benefit to only adding the observation code for properties that are explicitly marked “observable” is that the optional-check performance costs, as well as the memory allocation for the array of closures, can be skipped for properties that don’t need to be observable.

Alternatives Considered:

One could possibly base the observation around actual pointers, rather than strings. This would preclude any future extension to make this system interact with the existing KVO system, as well as necessitate some sort of rethinking about how bindings would be set up in XIB files, but it would add greater type safety (particularly, the compiler could enforce that observations could only be done on properties that were actually declared as observable).

One could introduce *two* arrays of observation closures, one to be fired before the setter runs and one afterward. This would work more similarly to how KVO works, but would introduce an extra performance cost, as each set would have to check *two* optionals rather than just one, and since the setter would have to wait for the “willSet” closure to finish before setting the property, which could slow down worker code. This would, however, eliminate the need for keeping a cached value for observable computed properties.

Charles

···

On Jan 1, 2016, at 7:00 PM, Jared Sinclair via swift-evolution <swift-evolution@swift.org> wrote:

The one-to-many observer pattern could really use a first-party, native Swift solution. The day-to-day practice of writing iOS / OS X applications needs it, and we end up falling back on antiquated APIs like KVO or NSNotificationCenter, or short-lived third-party libraries. This is an essential need that deserves a fresh approach.

What follows is a rough proposal for a Swift-native “KVO alternative”.

What Usage Would Look Like:

let tweet = Tweet(text: “Hi.”)
tweet.observables.isLiked.addObserver { (oldValue, newValue) -> Void in
    // this is otherwise just a standard closure, with identical
    // memory management rules and variable scope semantics.
    print(“Value changed.”)
}
tweet.isLiked = true // Console would log “Value changed.”

Behind the Scenes:

- When compiling a Swift class “Foo", the compiler would also generate a companion “Foo_SwiftObservables” class.

- When initializing an instance of a Swift class, an instance of the companion “ Foo_SwiftObservables” class would be initialized and set as the value of a reserved member name like “observables”. This member would be implicit, like `self`.

- The auto-generated “ Foo_SwiftObservables” class would have a corresponding property for every observable property of the target class, with an identical name.

- Each property of the auto-generated “ Foo_SwiftObservables” class would be an instance of a generic `Observable<T>` class, where `T` would be assigned to the value of the associated property of the target class.

- The `Observable<T>` class would have two public functions: addObserver() and removeObserver().

- The addObserver() function would take a single closure argument. This closure would have a signature like: (oldValue: T?, newValue: T?) -> Void.

- Observer closures would have the same memory management and variable scope rules as any other closure. Callers would not be obligated to remove their observer closures. Doing so would be a non-mandatory best practice.

Rough Code Examples

Consider a class for a Twitter client like:

class Tweet {
    var isLiked: Bool = false
    let text: String
     
    init(text: String) {
        self.text = text
    }
}
The compiler would generate a companion observables class:

class Tweet_SwiftObservables {
    let isLiked = Observable<Bool>()
}
Notice that only the `isLiked` property is carried over, since the `text` property of `Tweet` is a let, not a var.

The generic Observable class would be something like (pseudo-codish):

class Observable<T> {
    typealias Observer = (oldValue: T?, newValue: T?) -> Void
    private var observers = [UInt: Observer]()
     
    func addObserver(observer: Observer) -> Uint {
        let token: Uint = 0 // generate some unique token
        self.observers[token] = observer
        return token
    }
     
    func removeObserverForToken(token: Uint) {
        self.observers[token] = nil
    }
}

Benefits of This Approach

It’s familiar. It resembles the core mechanic of KVO without the hassle. It uses existing memory management rules. Everything you already understand about closures applies here.

It’s type-safe. The Observable<T> generic class ensures at compile-time that your observers don’t receive an incorrect type.

It’s readable. The syntax is brief without being unclear. Implementing the observation closure at the same call site as addObserver() keeps cause and effect as close together as possible.

It’s easy. It abandons a stringly-typed API in favor of a compile-time API. Since the Foo_SwiftObservables classes would be auto-generated by the compiler, there’s no need for busywork tasks like keeping redundant manual protocols or keyword constants up to date with the target classes.

Thanks for reading,

--
Jared Sinclair
@jaredsinclair
jaredsinclair.com <http://jaredsinclair.com/> _______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #3

Hi Jared,

I agree with your motivation, but I can’t see us having time to take this on in the Swift 3 timeframe. There is a huge potential design space here, and KVO has some significant sub-optimalities in its design - only one of which is that notifications often get delivered on the “wrong” thread.

It is pretty likely that we’ll get property behaviors in the Swift 3 timeframe, and that may enable some KVO like systems to be built. However, I doubt we would standardize and include a KVO system in the Swift 3 timeframe because of the short timeframes involved. Also, I’m very hopeful that we’ll be able to tackle concurrency in Swift 4, and making these two features mesh well seems really important.

That said, feel free to start hacking on the compiler to prototype your ideas!

-Chris

···

On Jan 1, 2016, at 5:00 PM, Jared Sinclair via swift-evolution <swift-evolution@swift.org> wrote:

The one-to-many observer pattern could really use a first-party, native Swift solution. The day-to-day practice of writing iOS / OS X applications needs it, and we end up falling back on antiquated APIs like KVO or NSNotificationCenter, or short-lived third-party libraries. This is an essential need that deserves a fresh approach.


(Maury Markowitz) #4

I'm confused about 'defer'. Not the purpose, the chosen syntax.

func confusing() {
    print("1")
    defer { print("2") }
    print("3")
}

Produces 1,3,2. Trying to describe what is happening here is non-trivial... it runs line 1, then we put line 2 on a stack, then line three runs, then we pop the stack... what?! And stepping through it in the debugger... ugh.

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this...

func clear() {
    print("1")
    print("3")

    always { print("2") }
}

Not only is the code clearly expressing its intent, the actual execution is following the source.


(Michel Fortin) #5

With your proposal, code like this:

func test() throws -> T {
  let a = try makeSomething()
  defer { a.cleanup() }

  let b = try makeSomething()
  defer { b.cleanup() }

  return try compute(a, b)
}

would have to be rewritten somewhat like this:

func test() throws -> T {
  let a = try makeSomething()
  do {
    let b = try makeSomething()
    do {
      return try compute(a, b)
      always { b.cleanup() }
    }
    always { a.cleanup() }
  }
}

which is a bit inconvenient. One important thing with `defer` is that where you put it in the function impacts at which point it gets pushed on the "cleanup stack", something that gets lost with `always` and which I need to add back through `do {}` blocks here.

I think what you are looking for is a `do {} finally {}` block, which you should feel free to propose if you think it's worth it. I personally don't think it makes the above example better, but it certainly has the benefit of always running things in source order.

func test() throws -> T {
  let a = try makeSomething()
  do {
    let b = try makeSomething()
    do {
      return try compute(a, b)
    } finally {
      b.cleanup()
    }
  } finally {
    a.cleanup()
  }
}

···

Le 2 janv. 2016 à 9:25, Maury Markowitz via swift-evolution <swift-evolution@swift.org> a écrit :

I'm confused about 'defer'. Not the purpose, the chosen syntax.

func confusing() {
   print("1")
   defer { print("2") }
   print("3")
}

Produces 1,3,2. Trying to describe what is happening here is non-trivial... it runs line 1, then we put line 2 on a stack, then line three runs, then we pop the stack... what?! And stepping through it in the debugger... ugh.

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this...

func clear() {
   print("1")
   print("3")

   always { print("2") }
}

Not only is the code clearly expressing its intent, the actual execution is following the source.

--
Michel Fortin
https://michelf.ca


(Tino) #6

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this…

In most cases, you use defer for cleanup tasks - so it make more sense to keep it at the source of the "problem":

file.open(); defer { file.close() }

Tino


(Nicky Gerritsen) #7

Defer is used to make code *always* run, even if the function terminates early. Imagine:

func doSomethingWith(x: Int) {
  print(“1”)
  defer { print(“2") }
  if x > 3 { defer { print(“yay”); return }
  print(“3”)
}

Now print(“2”) will always happen, even if the if is true. Placing it at the end this will not be the case. The “yay” will only be printed in the case of the if.
In general we can not place the defer at the end, because it only should run if the code “comes by” the call.

Regards,

Nicky

···

On 2 jan. 2016, at 15:25, Maury Markowitz via swift-evolution <swift-evolution@swift.org> wrote:

I'm confused about 'defer'. Not the purpose, the chosen syntax.

func confusing() {
   print("1")
   defer { print("2") }
   print("3")
}

Produces 1,3,2. Trying to describe what is happening here is non-trivial... it runs line 1, then we put line 2 on a stack, then line three runs, then we pop the stack... what?! And stepping through it in the debugger... ugh.

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this...

func clear() {
   print("1")
   print("3")

   always { print("2") }
}

Not only is the code clearly expressing its intent, the actual execution is following the source.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Robert Widmann) #8

-1. `defer` doesn’t exist just to execute code at the end of blocks, it exists to allow resource cleanup when you have a function with multiple return points or non-trivial scoping. For example, let’s add an if statement to your code:

func clear() {
   print("1")
   print("3")
   if (theBusiness) {
       print(“4”)
       return
   }
   always { print("2") }
}

Now `always` does not, in fact, model the flow of control through this function and I’m confused about where that finalizer is going to run. I mean, because it is declared below the if, will it never execute? Will it always execute as the name implies? But couldn’t control flow branch before that statement is hit? It’s a context switch I don’t have to make with `defer` as it currently stands.

···

On Jan 2, 2016, at 7:25 AM, Maury Markowitz via swift-evolution <swift-evolution@swift.org> wrote:

I'm confused about 'defer'. Not the purpose, the chosen syntax.

func confusing() {
   print("1")
   defer { print("2") }
   print("3")
}

Produces 1,3,2. Trying to describe what is happening here is non-trivial... it runs line 1, then we put line 2 on a stack, then line three runs, then we pop the stack... what?! And stepping through it in the debugger... ugh.

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this...

func clear() {
   print("1")
   print("3")

   always { print("2") }
}

Not only is the code clearly expressing its intent, the actual execution is following the source.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Sebastian Mecklenburg) #9

I don’t think it’s confusing, I read ‘defer’ a ‘do it later’ and that’s just what it does. And the deferred calls are not always necessary so they can’t always be placed at the end.

The only thing to be aware of is that the order is reversed (or not determined), so

func testDefer() {
    defer {print("deferred call1")}
    defer {print("deferred call2")}
}

prints

deferred call2
deferred call1

But I think that is the logical consequence of the way how defer is supposed to work, so that’s OK too. One just has to be aware of it.

···

On 02 Jan 2016, at 15:25, Maury Markowitz via swift-evolution <swift-evolution@swift.org> wrote:

I'm confused about 'defer'. Not the purpose, the chosen syntax.

func confusing() {
   print("1")
   defer { print("2") }
   print("3")
}

Produces 1,3,2. Trying to describe what is happening here is non-trivial... it runs line 1, then we put line 2 on a stack, then line three runs, then we pop the stack... what?! And stepping through it in the debugger... ugh.

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this...

func clear() {
   print("1")
   print("3")

   always { print("2") }
}

Not only is the code clearly expressing its intent, the actual execution is following the source.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Maury Markowitz) #10

Which is precisely why I called it 'always'. So in your example:

    func doSomethingWith(x: Int) {
  print(“1”)
  defer { print(“2") }
  if x > 3 { defer { print(“yay”); return }
  print(“3”)
    }

I would say:

    func doSomethingWith(x: Int) {
  print(“1”)
  print(“3”)
        always {
            print(“2")
            if x > 3 { print(“yay”) }
        }
    }

This is functionally identical, but both the syntax and program flow are greatly improved.

···

On Jan 2, 2016, at 9:38 AM, Nicky Gerritsen <nickygerritsen@me.com> wrote:

Defer is used to make code *always* run, even if the function terminates early. Imagine:


(Maury Markowitz) #11

I don’t think it’s confusing, I read ‘defer’ a ‘do it later’ and that’s just what it does. And the deferred calls are not always necessary so they can’t always be placed at the end.

Can you be more specific about "deferred calls are not always necessary"? Do you mean that you could, for instance, place an if in front of the defer? If so one could do the same with always, of course. I'll use your example, slightly expanded, to illustrate

func testDefer(x: Int) {
   defer {print("deferred call1")}
   if x > 1 { defer {print("deferred call2")} }

    print("non-deferred call")

}

I would rewrite this as:

    func testAlways(x: Int) {
        print("non-deferred call")
        always {
      print("deferred call1")
            if x > 1 print("deferred call2")
        }
    }

Which is 100% equivalent to your example, but works precisely as you would expect without needing to "be aware" of any "logical consequence"s. The code runs exactly as it appears.

···

On Jan 2, 2016, at 11:48 AM, Sebastian Mecklenburg via swift-evolution <swift-evolution@swift.org> wrote:


(Charles Srstka) #12

This. It’s way easier to remember to do necessary cleanup tasks if you add the cleanup call right after the call that requires the cleanup. It’s also much easier to catch cases where someone has forgotten to do so. Separating the init and cleanup by large distances as in the old try/catch/finally mechanism makes it easier for things to get out of sync as the code evolves.

Charles

···

On Jan 2, 2016, at 8:37 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Unless I missed something obvious, wouldn't placing "code that always has to run at the end" actually *at the end* not make more sense? Like this…

In most cases, you use defer for cleanup tasks - so it make more sense to keep it at the source of the "problem":

file.open(); defer { file.close() }

Tino


(Tim Hawkins) #13

Again my 2 cents

Other languages use "deffer", and have very simular semantics, there is no
benifit gained from being different, and it makes peoples task of
transfering from other systems easier.

The percieved "simplicity" of the alternative semanatics and naming is
subjective. What is there works just fine and achives the result it was
designed to do. Why do we need to change it?

···

On Jan 3, 2016 1:47 AM, "Maury Markowitz via swift-evolution" < swift-evolution@swift.org> wrote:

> On Jan 2, 2016, at 11:48 AM, Sebastian Mecklenburg via swift-evolution < > swift-evolution@swift.org> wrote:
>
> I don’t think it’s confusing, I read ‘defer’ a ‘do it later’ and that’s
just what it does. And the deferred calls are not always necessary so they
can’t always be placed at the end.

Can you be more specific about "deferred calls are not always necessary"?
Do you mean that you could, for instance, place an if in front of the
defer? If so one could do the same with always, of course. I'll use your
example, slightly expanded, to illustrate

> func testDefer(x: Int) {
> defer {print("deferred call1")}
> if x > 1 { defer {print("deferred call2")} }
    print("non-deferred call")
> }

I would rewrite this as:

    func testAlways(x: Int) {
        print("non-deferred call")
        always {
            print("deferred call1")
            if x > 1 print("deferred call2")
        }
    }

Which is 100% equivalent to your example, but works precisely as you would
expect without needing to "be aware" of any "logical consequence"s. The
code runs exactly as it appears.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jeremy Pereira) #14

Defer is used to make code *always* run, even if the function terminates early. Imagine:

Which is precisely why I called it 'always'. So in your example:

   func doSomethingWith(x: Int) {
  print(“1”)
  defer { print(“2") }
  if x > 3 { defer { print(“yay”); return }
  print(“3”)
   }

I would say:

   func doSomethingWith(x: Int) {
  print(“1”)
  print(“3”)
       always {
           print(“2")
           if x > 3 { print(“yay”) }
       }
   }

This is functionally identical, but both the syntax and program flow are greatly improved.

No your example is not functionally identical to Nicky’s (notwithstanding the missing closing brace in the original). “defer" defers the closure to the end of the current scope. In this instance , that is the end of the if block. The “yay” must come before the “2” because the if scope exits before the function scope. Also, in the following:

func doSomethingWith(x: Int) {
    print("1")
    defer { print("2") }
    if x > 3 { defer { print("yay") } }
    print("3")
}

doSomethingWith(4)

“yay” comes before “3” for the same reason.

···

On 2 Jan 2016, at 16:49, Maury Markowitz via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 2, 2016, at 9:38 AM, Nicky Gerritsen <nickygerritsen@me.com> wrote:

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Maury Markowitz) #15

Again my 2 cents

Other languages use "deffer", and have very simular semantics

No, they don't. With the exception of "Go", I'm unfamiliar with any other language that implements this feature *in this fashion*.

there is no benifit gained from being different, and it makes peoples task of transfering from other systems easier.

Precisely why I make this suggestion. Swift is the oddball here, using something that is decidedly non-standard as well as confusing. Why not use a solution that is widely used and better?

What is there works just fine and achives the result it was designed to do. Why do we need to change it?

The same could be said for Obj-C, yet here we are.

···

On Jan 2, 2016, at 12:56 PM, Tim Hawkins <tim.thawkins@gmail.com> wrote:


(Maury Markowitz) #16

Exactly the same way as 'defer' would behave if the function has an early return.

···

On Jan 2, 2016, at 1:26 PM, Javier Soto <javier.api@gmail.com> wrote:

How would always behave if the function has an early return?


(Michel Fortin) #17

D has `scope (exit)`, which is exactly the same as `defer` in Swift.

It's also common to see C++ code using scope guards (implemented as a library feature). As far as I'm aware, this is where the pattern emerged first.

···

Le 2 janv. 2016 à 13:29, Maury Markowitz via swift-evolution <swift-evolution@swift.org> a écrit :

No, they don't. With the exception of "Go", I'm unfamiliar with any other language that implements this feature *in this fashion*.

--
Michel Fortin
https://michelf.ca


(Robert S Mozayeni) #18

Not only that, but placing defer at the end of a scope, where any other code may never get executed if there’s an early return, kind of violates the whole concept of control flow.

func f() throws {
    let o = file(path: "")
    o.openFile()
    do {
        try o.write(self.data)
    }

    print("success")
    always { o.close() }
}

What happens if o.write fails? Going with always would imply that we either…

A) put the `always` in every possible place the scope might exit, defeating the whole purpose of defer/always. Maury, I’m assuming you’re not actually suggesting that, which would leave: B) if the main scope of the function exits at any point, drop down to the `always` at the end of the scope and execute it. But then, what about the surrounding code at the end of the main scope? Like I said, I think this would violate the whole concept of control flow by cherry-picking a specific type of command that is always executed within a scope, even if that command is in some place the control flow doesn’t reach.

Unless I’m misinterpreting something (let me know if I am) this seems less intuitive than `defer` was to begin with.

-Robert

···

On Jan 2, 2016, at 3:17 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:

Deferring at the end of the function removes the ability to defer actions on variables introduced in an inner scope.

On Sat, Jan 2, 2016, 1:57 PM Tino Heth via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I have the terrible feeling something is wrong with my posts so that they get caught by spamfilters or similar…

But as others stated as well:
defer has a use case that is a little bit different from what you want to archive.

> Why not use a solution that is widely used and better?
I'm curious:
Which languages have this "always" construct?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Robert S Mozayeni) #19

(Sorry if you get this email twice, I realized I first sent it from an email address that is not the one I used to subscribe to this list)

Not only that, but placing defer at the end of a scope, where any other code may never get executed if there’s an early return, kind of violates the whole concept of control flow.

func f() throws {
    let o = file(path: "")
    o.openFile()
    do {
        try o.write(self.data)
    }

    print("success")
    always { o.close() }
}

What happens if o.write fails? Going with always would imply that we either…

A) put the `always` in every possible place the scope might exit, defeating the whole purpose of defer/always. Maury, I’m assuming you’re not actually suggesting that, which would leave: B) if the main scope of the function exits at any point, drop down to the `always` at the end of the scope and execute it. But then, what about the surrounding code at the end of the main scope? Like I said, I think this would violate the whole concept of control flow by cherry-picking a specific type of command that is always executed within a scope, even if that command is in some place the control flow doesn’t reach.

Unless I’m misinterpreting something (let me know if I am) this seems less intuitive than `defer` was to begin with.

-Robert

···

On Jan 2, 2016, at 3:17 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:

Deferring at the end of the function removes the ability to defer actions on variables introduced in an inner scope.

On Sat, Jan 2, 2016, 1:57 PM Tino Heth via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I have the terrible feeling something is wrong with my posts so that they get caught by spamfilters or similar…

But as others stated as well:
defer has a use case that is a little bit different from what you want to archive.

> Why not use a solution that is widely used and better?
I'm curious:
Which languages have this "always" construct?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Javier Soto) #20

How would always behave if the function has an early return? Like so:

func testAlways(x: Int) {
print("non-deferred call")
return
let a = 3
always {
print("deferred call: \(a)")
}
}

···

On Sat, Jan 2, 2016 at 9:56 AM Tim Hawkins via swift-evolution < swift-evolution@swift.org> wrote:

Again my 2 cents

Other languages use "deffer", and have very simular semantics, there is no
benifit gained from being different, and it makes peoples task of
transfering from other systems easier.

The percieved "simplicity" of the alternative semanatics and naming is
subjective. What is there works just fine and achives the result it was
designed to do. Why do we need to change it?
On Jan 3, 2016 1:47 AM, "Maury Markowitz via swift-evolution" < > swift-evolution@swift.org> wrote:

> On Jan 2, 2016, at 11:48 AM, Sebastian Mecklenburg via swift-evolution < >> swift-evolution@swift.org> wrote:
>
> I don’t think it’s confusing, I read ‘defer’ a ‘do it later’ and that’s
just what it does. And the deferred calls are not always necessary so they
can’t always be placed at the end.

Can you be more specific about "deferred calls are not always necessary"?
Do you mean that you could, for instance, place an if in front of the
defer? If so one could do the same with always, of course. I'll use your
example, slightly expanded, to illustrate

> func testDefer(x: Int) {
> defer {print("deferred call1")}
> if x > 1 { defer {print("deferred call2")} }
    print("non-deferred call")
> }

I would rewrite this as:

    func testAlways(x: Int) {
        print("non-deferred call")
        always {
            print("deferred call1")
            if x > 1 print("deferred call2")
        }
    }

Which is 100% equivalent to your example, but works precisely as you
would expect without needing to "be aware" of any "logical consequence"s.
The code runs exactly as it appears.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Javier Soto