What about a VBA style with Statement?


(Michael Peternell) #1

I think the idea is good, but I think it would be bad for the language overall.

-1

As a plus, it makes code easier to write, sometimes; or at least it seems so. On the other hand, I think it makes code harder to comprehend. A `with` statement introduces a new scope in which it is not obvious what exactly happens. For example:

with(someObject) {
    foo()
}

what does it do? The mere fact that I wrote `with(someObject)` suggests that `someObject.foo()` is what should happen. But it could also mean `self.foo()` or just the global function `foo()`.

Usually, you can see from the lexical surrounding of a call, what is happening. `foo()` means either `self.foo()`, or it means `foo()`. It's okay that I, as the class author know if my class has a `foo()` method or not. But do I have to know the API of someObject completely, including any future changes to its API?

For example: Suppose I have a class Foo, which has a method foo(). A remote co-worker wrote class Bar, which has a method bar(). Because I need bar a lot recently, I decided to use the with() form:

with(someBarObject) {
   ...
   foo()
   ...
   bar()
   ...
   bar()
   ...
   foo()
}

All is fine, it's clear that foo() calls my method and bar() calls that method on the someBarObject object. Then, suddenly, my co-worker had a great idea! He also implements foo() - it cannot break any existing code doing so, because existing code will not call foo(), because up until now class Bar did not implement foo(), after all...

Suddenly, my Foo class breaks, because my co-worker implemented foo() in his own class :-o

Such a thing really shouldn't happen. I am arguing that `with` is an inherently unsafe language feature! I also think it doesn't make the code actually easier to read. It may be an advantage if you have some reallyReallyLongAndFunnyVariableNames_withUnderscores_andSpecialTypeAnnotations, but even then I think it isn't worth it. I prefer easy-to-read over easy-to-write.

I like it when I can know what some code does, just by looking at it locally. These are syntactic guarantees that I can depend on. E.g. when I read something like

    x.bar()
    foo()

I know that there is an x variable somewhere (either in the current instance, or globally, or a local variable; a global variable is unlikely, because no one should dare to call a local variable just x.)
I know that the bar() method is called on that variable x, so x implements the bar() method. Either itself, or through an extension.
foo() is either a global method, or an instance method, or a static method.
All of these questions can be answered if I just know the class that I am currently implementing. If I know that my class didn't implement foo(), then foo() must be global. If x is a local var, an iVar or a static var, I can tell just by looking at the surrounding code. If it isn't => it must be global.
This (let me call it) "lexical decidability" is reduced by a `with`-operator, and this is the primary reason why I don't like it. For exactly this reason, I use a convention when writing Objective C code, that iVars should always be prefixed by an underscore, static and global vars always start with an uppercase letter, and all local vars and parameters start with a lowercase letter.

···

***

Even if we use .bar() instead of just bar() when calling from within a `with` block, I still don't like it. How many keystrokes are we sparing? If the variable has two letters, e.g. it's named `br`, we have `with(br){` (9 characters, 1 newline) and `}` (1 character, 1 newline) compared to 2 keystrokes for each time you may omit `br`. If you count the newlines as characters, you have to use the variable br 7 times in order for the `with` statement to pay off.

***

Using { $0.bar() } is interesting too. But why?? Just give the variable a short name, call it x0, for example, and you are saving even more keystrokes.

***

IMHO, saving sourecode-bits is not everything; readable code is everything. Therefore, a -1 from me.

-Michael


(Vladimir) #2

Yes, I agree, that "implicit" "with", where we can't say if method belongs to the target instance or not, is very bad idea. I strongly agains it.

But the proposal is for "explicit" "with", where it is clear what method/property belongs to target instance, and which don't. So most of your notes regarding problems with "with" are not related to "external" with, but to "internal" "with", that was not even introduced by the proposal.

As for your notes regarding implicit "self." when you writing code inside class. No, you not always know what exact methods is in class and what is external functions: in case you are extending some class from some library or inherit from such class. And I think we actually have this problem when writing class code : I believe we need "explicit" marker that some method/property belongs to our class. Personally I think "self." should be necessarily to use in our code.

>Even if we use .bar() instead of just bar() when calling from within.....

It is not about saving the space in code. It is about more readable and (I insist) more stable(with less errors) code.

Let's get your example with br. Let's say you have additionally bt instance. Take into account code-completion feature of your editor. Take into account copy-paste errors(when you copy code of br, wants to use for bt).

br = ..
br.one = ..
br.two = ...
br.three = ...
some1 = br.four()

bt = ..
bt.one = ..
bt.two = ...
br.three = ...
some2 = bt.four()

oops.. "br.three = ..." instead of "bt.three"

but let's compare :

with br {
   .one = ..
   .two = ...
   .three = ...
   some1 = .four()
}

with bt {
   .one = ..
   .two = ...
   .three = ...
   some2 = .four()
}

Much less possibilities for copy-paste errors. Wrong code completion suggestion(by editor) can not produce error. It is explicit and clear, it has much less noise in code. IMO it just better in any ways.

> IMHO, saving sourecode-bits is not everything; readable code is
> everything. Therefore, a -1 from me.

This proposal is not about saving bits. And it is about more readable code. As I understand you are -1 for "implicit" "where". I'm too.

···

On 13.04.2016 22:48, Michael Peternell via swift-evolution wrote:

I think the idea is good, but I think it would be bad for the language overall.

-1

As a plus, it makes code easier to write, sometimes; or at least it seems so. On the other hand, I think it makes code harder to comprehend. A `with` statement introduces a new scope in which it is not obvious what exactly happens. For example:

with(someObject) {
     foo()
}

what does it do? The mere fact that I wrote `with(someObject)` suggests that `someObject.foo()` is what should happen. But it could also mean `self.foo()` or just the global function `foo()`.

Usually, you can see from the lexical surrounding of a call, what is happening. `foo()` means either `self.foo()`, or it means `foo()`. It's okay that I, as the class author know if my class has a `foo()` method or not. But do I have to know the API of someObject completely, including any future changes to its API?

For example: Suppose I have a class Foo, which has a method foo(). A remote co-worker wrote class Bar, which has a method bar(). Because I need bar a lot recently, I decided to use the with() form:

with(someBarObject) {
    ...
    foo()
    ...
    bar()
    ...
    bar()
    ...
    foo()
}

All is fine, it's clear that foo() calls my method and bar() calls that method on the someBarObject object. Then, suddenly, my co-worker had a great idea! He also implements foo() - it cannot break any existing code doing so, because existing code will not call foo(), because up until now class Bar did not implement foo(), after all...

Suddenly, my Foo class breaks, because my co-worker implemented foo() in his own class :-o

Such a thing really shouldn't happen. I am arguing that `with` is an inherently unsafe language feature! I also think it doesn't make the code actually easier to read. It may be an advantage if you have some reallyReallyLongAndFunnyVariableNames_withUnderscores_andSpecialTypeAnnotations, but even then I think it isn't worth it. I prefer easy-to-read over easy-to-write.

I like it when I can know what some code does, just by looking at it locally. These are syntactic guarantees that I can depend on. E.g. when I read something like

     x.bar()
     foo()

I know that there is an x variable somewhere (either in the current instance, or globally, or a local variable; a global variable is unlikely, because no one should dare to call a local variable just x.)
I know that the bar() method is called on that variable x, so x implements the bar() method. Either itself, or through an extension.
foo() is either a global method, or an instance method, or a static method.
All of these questions can be answered if I just know the class that I am currently implementing. If I know that my class didn't implement foo(), then foo() must be global. If x is a local var, an iVar or a static var, I can tell just by looking at the surrounding code. If it isn't => it must be global.
This (let me call it) "lexical decidability" is reduced by a `with`-operator, and this is the primary reason why I don't like it. For exactly this reason, I use a convention when writing Objective C code, that iVars should always be prefixed by an underscore, static and global vars always start with an uppercase letter, and all local vars and parameters start with a lowercase letter.

***

Even if we use .bar() instead of just bar() when calling from within a `with` block, I still don't like it. How many keystrokes are we sparing? If the variable has two letters, e.g. it's named `br`, we have `with(br){` (9 characters, 1 newline) and `}` (1 character, 1 newline) compared to 2 keystrokes for each time you may omit `br`. If you count the newlines as characters, you have to use the variable br 7 times in order for the `with` statement to pay off.

***

Using { $0.bar() } is interesting too. But why?? Just give the variable a short name, call it x0, for example, and you are saving even more keystrokes.

***

IMHO, saving sourecode-bits is not everything; readable code is everything. Therefore, a -1 from me.

-Michael

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


(Brent Royal-Gordon) #3

As a plus, it makes code easier to write, sometimes; or at least it seems so. On the other hand, I think it makes code harder to comprehend. A `with` statement introduces a new scope in which it is not obvious what exactly happens. For example:

with(someObject) {
   foo()
}

what does it do? The mere fact that I wrote `with(someObject)` suggests that `someObject.foo()` is what should happen. But it could also mean `self.foo()` or just the global function `foo()`.

I support having two separate, but interlocking, features:

* A `with(_:user:)` function in the standard library which can access, and perhaps modify, a value.
* The ability to name a closure parameter `self`, which would make bare method calls be directed to that parameter.

So if your code said:

  with(someBarObject) { self in
    ...
    foo()
    ...
    bar()
    ...
    bar()
    ...
    foo()
  }

Then the foo() and bar() calls would definitely be either on someBarObject, or to global functions, whereas if you said:

  with(someBarObject) { value in
    ...
    foo()
    ...
    bar()
    ...
    bar()
    ...
    foo()
  }

Then they would definitely be on either the closed-over `self`, or to global functions.

(In no case, however, should it be possible that a call could refer to any of the three.)

···

--
Brent Royal-Gordon
Architechies


(Erica Sadun) #4

Could this would this tie into the weak-strong dance? And if so, how?

-- E

···

On Apr 14, 2016, at 2:55 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

* The ability to name a closure parameter `self`, which would make bare method calls be directed to that parameter.


(Brent Royal-Gordon) #5

* The ability to name a closure parameter `self`, which would make bare method calls be directed to that parameter.

Could this would this tie into the weak-strong dance? And if so, how?

Actually, one of my motivations for wanting `self` parameters is the block-based undo registration API:

  func add(_ amount: Int) {
    value += amount
    undoManager.registerUndo(target: self) { self in subtract(amount) }
  }

Since undo managers reference targets weakly, this API encapsulates the weak-strong dance for you. But without `self` parameters, you end up having to invent a different name:

  func add(_ amount: Int) {
    value += amount
    undoManager.registerUndo(target: self) { target in target.subtract(amount) }
  }

And if you forget and accidentally use `self`, the block references `self` strongly, which could create a retain cycle. So `self` parameters would make this pattern—which is really the best way to handle a weak-strong dance—safer and cleaner, encouraging its use.

···

--
Brent Royal-Gordon
Architechies