continuations - "extensions on steroids" idea

to sum up. so far the feedback on this proposal was:

1) generally in favour (e.g. to have ability of adding variables and
accessing privates at all)

2) the name "continuation" is used for something else

3) why not to use partials as they are in c#

4) having explicit names for continuations is unwanted because naming is
hard

5) the ledger list is unnecessary as anyone on the same module will be able
to change it anyway - false feel of protection.

here are my thoughts on it.

1) "generally in favour (e.g. to have ability of adding variables and
accessing privates at all)"
-- great! thank you.

2) "the name "continuation" is used for something else"
-- thought the same. let it be "part" instead of continuation

3) "why not to use partials as they are in c#"
-- my belief here is that just because i made my type partial (for my own
reasons, e.g. as a result of splitting a single-file class into a
multi-file) it does not necessarily mean I want other developers of my team
(on the same module) to add continuations / parts to my class. in other
words, while there are the module boundaries (the building walls) i still
want to see some partitions between the rooms of that building to have some
privacy.

4) "having explicit names for continuations is unwanted because naming is
hard"
-- every time I am adding extension now I want to label it somehow to
indicate it's purpose. if that extensions adds a protocol conformance (e.g.
"extension ViewController: UITableViewDataSource") the problem is not as
critical as the protocol (or the list of protocols) name itself can serve
the purpose of such an indication. if there is no such a protocol
conformance however i tend to add a "MARK: ThePurpose" or a comment
("extension ViewController /* ThePurpose */) and as the comments are not
checked and get out of sync every time i do this i wish there was a a more
explicit extension label in the language for this purpose. maybe that's
just me.

5) "the ledger list is unnecessary as anyone on the same module will be
able to change it anyway - false feel of protection."
-- to this i can give the same response as in (3). here is another example
that hopefully will clarify my point: we shall not really say that
"private" in swift is useless and "internal" shall be used instead of it
just because anyone in the same module can bypass it anyway: go to your
class and change the source from "private" to "internal" for their own
benefits, so why bother with private / fileprivate to begin with. so is
true in regards to the ledger: yes, it is true that anyone on the team
working on the same module has a physical ability to go to my class (the
class who's sole maintainer and "owner" is myself) and mess around it,
changing it's ledger along the way, or making it partial as in (3) or
changing it's privates to internal, or adding variables, etc. it's just
they shouldn't, at least not talking to me first. they won't be "behaving
properly" if they do.

some additional thoughts. ledger works similar to the c++ class definition
itself, which lists all the members of the class, just on a less granular
scope: while in C++ you have to go there every time you want to add, say, a
private method, with parts you go change the ledger very infrequently, to
add a group or functionalities. another tangentially similar feature of C++
is "friends". if you class have, say, 10 different extensions each
implementing a certain feature and you converted them to "parts" the ledger
list will contain 10 entries naming those features explicitly.

Mike

ps. by now i figured that discussing protection levels in swift is akin to
having a wonderful morning walk across a mine field

I will echo what several other people said in the previous discussion: you have to trust your fellow developers. By declaring a class as “partial” you are allowing that class to be extended elsewhere. Typically that would mean in an adjacent file named ClassName.Foo.swift (next to ClassName.swift). It should be highly discouraged to extend the class using partial from anywhere else, and listing tools can enforce that if needed. But at the end of the day if you can’t trust your fellow developers not to do that then you can’t trust them not to add a new name to the ledger either.

And in case someone was wondering why I’m ok with this and not the “classprivate" idea, the key difference here is that this is opt-in. I’m still not ok with a random extension being able to access private fields of any class by default. Partial classes should be an exception for specific use cases, not a default behavior.

···

On Nov 2, 2017, at 5:18 AM, Mike Kluev via swift-evolution <swift-evolution@swift.org> wrote:

to sum up. so far the feedback on this proposal was:

1) generally in favour (e.g. to have ability of adding variables and accessing privates at all)

2) the name "continuation" is used for something else

3) why not to use partials as they are in c#

4) having explicit names for continuations is unwanted because naming is hard

5) the ledger list is unnecessary as anyone on the same module will be able to change it anyway - false feel of protection.

here are my thoughts on it.

1) "generally in favour (e.g. to have ability of adding variables and accessing privates at all)"
-- great! thank you.

2) "the name "continuation" is used for something else"
-- thought the same. let it be "part" instead of continuation

3) "why not to use partials as they are in c#"
-- my belief here is that just because i made my type partial (for my own reasons, e.g. as a result of splitting a single-file class into a multi-file) it does not necessarily mean I want other developers of my team (on the same module) to add continuations / parts to my class. in other words, while there are the module boundaries (the building walls) i still want to see some partitions between the rooms of that building to have some privacy.

4) "having explicit names for continuations is unwanted because naming is hard"
-- every time I am adding extension now I want to label it somehow to indicate it's purpose. if that extensions adds a protocol conformance (e.g. "extension ViewController: UITableViewDataSource") the problem is not as critical as the protocol (or the list of protocols) name itself can serve the purpose of such an indication. if there is no such a protocol conformance however i tend to add a "MARK: ThePurpose" or a comment ("extension ViewController /* ThePurpose */) and as the comments are not checked and get out of sync every time i do this i wish there was a a more explicit extension label in the language for this purpose. maybe that's just me.

5) "the ledger list is unnecessary as anyone on the same module will be able to change it anyway - false feel of protection."
-- to this i can give the same response as in (3). here is another example that hopefully will clarify my point: we shall not really say that "private" in swift is useless and "internal" shall be used instead of it just because anyone in the same module can bypass it anyway: go to your class and change the source from "private" to "internal" for their own benefits, so why bother with private / fileprivate to begin with. so is true in regards to the ledger: yes, it is true that anyone on the team working on the same module has a physical ability to go to my class (the class who's sole maintainer and "owner" is myself) and mess around it, changing it's ledger along the way, or making it partial as in (3) or changing it's privates to internal, or adding variables, etc. it's just they shouldn't, at least not talking to me first. they won't be "behaving properly" if they do.

some additional thoughts. ledger works similar to the c++ class definition itself, which lists all the members of the class, just on a less granular scope: while in C++ you have to go there every time you want to add, say, a private method, with parts you go change the ledger very infrequently, to add a group or functionalities. another tangentially similar feature of C++ is "friends". if you class have, say, 10 different extensions each implementing a certain feature and you converted them to "parts" the ledger list will contain 10 entries naming those features explicitly.

Mike

ps. by now i figured that discussing protection levels in swift is akin to having a wonderful morning walk across a mine field

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

IMO the ledger isn’t just about access control, it’s also about having a convenient (and guaranteed correct, due compiler enforcement) place to see where the rest of your class is defined.

I’m +1 on the ledger and partial classes in general. I think extensions serving the dual purpose of extending other module’s classes, as well as an organizational tool for in-module classes has resulted in some bad choices and makes future development harder. Having a new construct for in-module class organization neatly solves the problem.

(I still want C++’s protected scope too though, in case there was any doubt).

···

On Nov 2, 2017, at 9:37 PM, Adam Kemp via swift-evolution <swift-evolution@swift.org> wrote:

I will echo what several other people said in the previous discussion: you have to trust your fellow developers. By declaring a class as “partial” you are allowing that class to be extended elsewhere. Typically that would mean in an adjacent file named ClassName.Foo.swift (next to ClassName.swift). It should be highly discouraged to extend the class using partial from anywhere else, and listing tools can enforce that if needed. But at the end of the day if you can’t trust your fellow developers not to do that then you can’t trust them not to add a new name to the ledger either.

And in case someone was wondering why I’m ok with this and not the “classprivate" idea, the key difference here is that this is opt-in. I’m still not ok with a random extension being able to access private fields of any class by default. Partial classes should be an exception for specific use cases, not a default behavior.

I like the ledger list idea better than saying “this class is partial” and now it can be extended anywhere.

Adam said below that, “you have to trust your fellow developers,” but that’s BS. You can’t merge a PR to master without approval, and some repos you have to PR from a fork.

It’s easy to have projects where one Swift module is made up of numerous git repos. You can silo who is allowed to touch your code that way even within people working on a single module.

Having a class “partial” where anyone in the module can screw around is dangerous and therefore un-Swifty.

If devs could be trusted then Swift and git would not even exist.

So I feel it has to be the ledger, guys :smiley: Just saying.

J

···

On Nov 2, 2017, at 18:37, Adam Kemp via swift-evolution <swift-evolution@swift.org> wrote:

I will echo what several other people said in the previous discussion: you have to trust your fellow developers. By declaring a class as “partial” you are allowing that class to be extended elsewhere. Typically that would mean in an adjacent file named ClassName.Foo.swift (next to ClassName.swift). It should be highly discouraged to extend the class using partial from anywhere else, and listing tools can enforce that if needed. But at the end of the day if you can’t trust your fellow developers not to do that then you can’t trust them not to add a new name to the ledger either.

And in case someone was wondering why I’m ok with this and not the “classprivate" idea, the key difference here is that this is opt-in. I’m still not ok with a random extension being able to access private fields of any class by default. Partial classes should be an exception for specific use cases, not a default behavior.

On Nov 2, 2017, at 5:18 AM, Mike Kluev via swift-evolution <swift-evolution@swift.org> wrote:

to sum up. so far the feedback on this proposal was:

1) generally in favour (e.g. to have ability of adding variables and accessing privates at all)

2) the name "continuation" is used for something else

3) why not to use partials as they are in c#

4) having explicit names for continuations is unwanted because naming is hard

5) the ledger list is unnecessary as anyone on the same module will be able to change it anyway - false feel of protection.

here are my thoughts on it.

1) "generally in favour (e.g. to have ability of adding variables and accessing privates at all)"
-- great! thank you.

2) "the name "continuation" is used for something else"
-- thought the same. let it be "part" instead of continuation

3) "why not to use partials as they are in c#"
-- my belief here is that just because i made my type partial (for my own reasons, e.g. as a result of splitting a single-file class into a multi-file) it does not necessarily mean I want other developers of my team (on the same module) to add continuations / parts to my class. in other words, while there are the module boundaries (the building walls) i still want to see some partitions between the rooms of that building to have some privacy.

4) "having explicit names for continuations is unwanted because naming is hard"
-- every time I am adding extension now I want to label it somehow to indicate it's purpose. if that extensions adds a protocol conformance (e.g. "extension ViewController: UITableViewDataSource") the problem is not as critical as the protocol (or the list of protocols) name itself can serve the purpose of such an indication. if there is no such a protocol conformance however i tend to add a "MARK: ThePurpose" or a comment ("extension ViewController /* ThePurpose */) and as the comments are not checked and get out of sync every time i do this i wish there was a a more explicit extension label in the language for this purpose. maybe that's just me.

5) "the ledger list is unnecessary as anyone on the same module will be able to change it anyway - false feel of protection."
-- to this i can give the same response as in (3). here is another example that hopefully will clarify my point: we shall not really say that "private" in swift is useless and "internal" shall be used instead of it just because anyone in the same module can bypass it anyway: go to your class and change the source from "private" to "internal" for their own benefits, so why bother with private / fileprivate to begin with. so is true in regards to the ledger: yes, it is true that anyone on the team working on the same module has a physical ability to go to my class (the class who's sole maintainer and "owner" is myself) and mess around it, changing it's ledger along the way, or making it partial as in (3) or changing it's privates to internal, or adding variables, etc. it's just they shouldn't, at least not talking to me first. they won't be "behaving properly" if they do.

some additional thoughts. ledger works similar to the c++ class definition itself, which lists all the members of the class, just on a less granular scope: while in C++ you have to go there every time you want to add, say, a private method, with parts you go change the ledger very infrequently, to add a group or functionalities. another tangentially similar feature of C++ is "friends". if you class have, say, 10 different extensions each implementing a certain feature and you converted them to "parts" the ledger list will contain 10 entries naming those features explicitly.

Mike

ps. by now i figured that discussing protection levels in swift is akin to having a wonderful morning walk across a mine field

_______________________________________________
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

I have worked on several large code bases in C# that made extensive use of partial classes. This was never a problem. No one ever tried to put a partial class where it didn’t belong.

Would it be an error to have an entry in the “ledger” but not a corresponding implementation?

···

On Nov 2, 2017, at 7:52 PM, Noah Desch <deschnl@icloud.com> wrote:

IMO the ledger isn’t just about access control, it’s also about having a convenient (and guaranteed correct, due compiler enforcement) place to see where the rest of your class is defined.

definitely so. and vice versa.

Mike

···

On 3 November 2017 at 03:05, Adam Kemp <adam.kemp@apple.com> wrote:

Would it be an error to have an entry in the “ledger” but not a
corresponding implementation?

I’m +1 on the ledger and partial classes in general. I think extensions
serving the dual purpose of extending other module’s classes, as well as an
organizational tool for in-module classes has resulted in some bad choices
and makes future development harder. Having a new construct for in-module
class organization neatly solves the problem.

well said

(I still want C++’s protected scope too though, in case there was any
doubt).

+1

Mike

···

On 3 November 2017 at 02:52, Noah Desch <deschnl@icloud.com> wrote:

It’s easy to have projects where one Swift module is made up of numerous git repos. You can silo who is allowed to touch your code that way even within people working on a single module.

Why do you need to have multiple git repos contribute to a single module instead of having each repo produce its own module? I don’t understand why you would do that.

To me “module” implies “a cohesive set of code maintained by a single team”. I trust people on my team, and I can also see every change that goes into the projects I work on.

Again, if you don’t trust developers that are contributing code to your module then you have bigger problems than this. Proper module boundaries would help to address those problems.

Having a class “partial” where anyone in the module can screw around is dangerous and therefore un-Swifty.

This is contradicted by the experience of a large community of C# developers who have been using partial classes for years without running into your hypothetical problems.

A search on GitHub shows over 5 million hits for “partial class” in C# code:
https://github.com/search?l=C%23&o=desc&q=“partial+class”&s=indexed&type=Code <https://github.com/search?l=C#&q=“partial+class”&type=Code>

There are over 2000 hits in the Rosyln codebase alone (Microsoft’s C# compiler):

https://github.com/dotnet/roslyn/search?q="partial+class"&type= <https://github.com/dotnet/roslyn/search?q="partial+class"&type=>

This is heavily used in the real world by professionals working on projects of all sizes.

I think we have to weigh the FUD against what we can observe actually happening in the wild when developers use a feature like this. The reality is that the things you’re concerned about are not actually problems in practice. They’re contrived hypotheticals.

···

On Nov 7, 2017, at 6:05 PM, Jon Gilbert via swift-evolution <swift-evolution@swift.org> wrote:

I haven’t seen real resistance against partial types so far — and why should somebody oppose? Hopefully, no one who doesn’t like this feature will be forced to use it, right?

But imho that’s a slippery path, because there’s still a cost associated with every feature that is completely new:
Increased complexity.

I have no doubt that some people would find this feature cool, and build code with it, but I know that I personally don’t need it, and that there are things I consider to be more important.

Imho it’s at least worth a debate if splitting a type is actually that useful: We can already blame it for the trouble with access rights, so I’d rather prefer to postpone the topic in favor of changes that decrease complexity by removing limitations on existing features (for example, I’d like to be able to override something declared in an extension of a superclass…)

If that’s the case then this wouldn’t solve one of the major use cases I listed for this: splitting up cross-platform classes into platform-specific files. The idea is that each target contains a different subset of the files. Consider a case where two platforms are similar enough to share some code but not all code. For instance:

Class.swift
Class.UIKit.swift // Used by iOS + watchOS
Class.iOS.swift
Class.watchOS.swift
Class.macOS.swift

There are three targets, and two of the targets include Class.UIKit.swift but not the third. So what do you put in the ledger?

For a real-world example, consider a custom view that is used for presenting content in an app that shares code on iOS, tvOS, and macOS. Much of the logic for this is shared, including rendering which is done using CoreGraphics. You could write that class something like this:

PresentationView.swift:
import CoreGraphics

partial class PresentationView {

private func draw(inContext context: CGContext) {
// Shared CoreGraphics drawing
}

}

PresentationView.UIKit.swift: // Used by iOS + tvOS
import UIKit

partial class PresentationView : UIView {
func draw(_ rect: CGRect) {
self.draw(inContext: UIGraphicsGetCurrentContext())
}
}

PresentationView.AppKit.swift:
import AppKit

partial class PresentationView : NSView {
func draw(_ dirtyRect: NSRect) {
self.draw(inContext: NSGraphicsContext.currentContext()?.CGContext)
}
}

This is exactly the technique that I’ve used in the past (in C#) to share code across iOS, Android, macOS, and WPF. It’s really powerful, but the ledger would make it much harder.

···

On Nov 3, 2017, at 5:17 AM, Mike Kluev <mike.kluev@gmail.com> wrote:

On 3 November 2017 at 03:05, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

Would it be an error to have an entry in the “ledger” but not a corresponding implementation?

definitely so. and vice versa.

Mike

i think we need a better real-world example, as in this one it's probably
easier to have "View" defined as a type alias to either UIView or NSView
depending upon a platform and similarly define currentGraphicsContext() to
be either UIGraphicsGetCurrentContext() or
NSGraphicsContext.currentContext()?.CGContext depending upon a platform and
have a single code base afterwords:

class PresentationView: View {

    private func draw(inContext context: CGContext) {

        // Shared CoreGraphics drawing

    }

    func draw(_ rect: CGRect) {

        self.draw(inContext: currentGraphicsContext())

    }

}

having said that, yes, i can see your point. my fear is that it will be (1)
too fragile (e.g. you have "TableViewDelegate" in the ledger and just
forgot to include the relevant file in the target - the app compiles but
then misbehaves, and (2) open for abuse.

alternative 1 - it's not too hard to put an empty:

part Feature of SomeClass {}

for the relevant platform

alternative 2 - have this in the ledger:

class Some {

    part Feature1

    optional part Feature2

}

as an "opt-in" to the behaviour you want.

i think this type of refinement can be done at some later stage. after all,
we do not have even (much needed IMHO) optional protocol methods without
resorting to @objc as of now...

Mike

···

On 3 November 2017 at 16:42, Adam Kemp <adam_kemp@apple.com> wrote:

If that’s the case then this wouldn’t solve one of the major use cases I
listed for this: splitting up cross-platform classes into platform-specific
files. The idea is that each target contains a different subset of the
files. Consider a case where two platforms are similar enough to share some
code but not all code. For instance:

If that’s the case then this wouldn’t solve one of the major use cases I listed for this: splitting up cross-platform classes into platform-specific files. The idea is that each target contains a different subset of the files. Consider a case where two platforms are similar enough to share some code but not all code. For instance:

i think we need a better real-world example, as in this one it's probably easier to have "View" defined as a type alias to either UIView or NSView depending upon a platform and similarly define currentGraphicsContext() to be either UIGraphicsGetCurrentContext() or NSGraphicsContext.currentContext()?.CGContext depending upon a platform and have a single code base afterwords:

When you actually try to use that technique in a fuller example it becomes impractical. I know because some people working on the same code base tried that, and it was much worse. Partial classes make for a much cleaner implementation, and they avoid conditional compilation.

I will concede that there are other techniques for cross-platform code that might be considered even cleaner. This isn’t the only technique, and I won’t claim that it’s the best technique, but it is a very useful technique that I have seen used very effectively. It would be nice to be able to have it as an option.

having said that, yes, i can see your point. my fear is that it will be (1) too fragile (e.g. you have "TableViewDelegate" in the ledger and just forgot to include the relevant file in the target - the app compiles but then misbehaves,

My argument is that there should be no ledger in the first place. IMO you haven’t made the case for that requirement.

If you forget to implement a protocol then your build will fail because either 1) you claimed to implement it and didn’t or 2) you didn’t claim to implement it and then tried to use that class somewhere that expects an object conforming to the protocol. This was never a problem. We have a type safe language. Even if you come up with some corner case where this could actually happen it falls squarely into the realm of bugs that would never ship (i.e., it will be obvious very quickly that something is wrong).

and (2) open for abuse.

Again, we have to trust our developers to some extent. I worked in a large codebase with a large team of developers and not once did anyone abuse this.

···

On Nov 3, 2017, at 10:05 AM, Mike Kluev <mike.kluev@gmail.com> wrote:
On 3 November 2017 at 16:42, Adam Kemp <adam_kemp@apple.com <mailto:adam_kemp@apple.com>> wrote:

When you actually try to use that technique in a fuller example it becomes
impractical. I know because some people working on the same code base tried
that, and it was much worse.

please provide more details on this. i was and still using such a
technique, so want to be prepared for those pitfalls before i actually
encounter them.

I will concede that there are other techniques for cross-platform code
that might be considered even cleaner. This isn’t the only technique, and I
won’t claim that it’s the best technique, but it is a very useful technique
that I have seen used very effectively. It would be nice to be able to have
it as an option.

having said that, yes, i can see your point. my fear is that it will be
(1) too fragile (e.g. you have "TableViewDelegate" in the ledger and just
forgot to include the relevant file in the target - the app compiles but
then misbehaves,

My argument is that there should be no ledger in the first place. IMO you
haven’t made the case for that requirement.

If you forget to implement a protocol then ...

i mean this:

class MyView: UIView {
    optional part Drawing
}

part Drawing of MyView { // **** forgot to include this into target
    override func drawRect(...) {
        .....
    }
}

the app compiles but doesn't work correctly.

Mike

···

On 3 November 2017 at 17:23, Adam Kemp <adam_kemp@apple.com> wrote:

When you actually try to use that technique in a fuller example it becomes impractical. I know because some people working on the same code base tried that, and it was much worse.

please provide more details on this. i was and still using such a technique, so want to be prepared for those pitfalls before i actually encounter them.

1. You end up with a whole section of type aliases at the top of the file, and as you read the file it’s hard to understand what those types actually represent. Which methods are you actually allowed to use? What should code completion show you? Nothing is as it seems.

2. The type alias is only useful when the naming of the methods/properties is identical and the sequence of calls you have to make is identical, which very often isn’t the case. You end up needing conditional compilation anyway for cases where you only need to make a call on one platform, or where the method you have to call is named slightly different or takes different arguments.

Out of the strategies I’ve seen used for cross-platform code this one was the most frustrating to work with in my experience.

My argument is that there should be no ledger in the first place. IMO you haven’t made the case for that requirement.

If you forget to implement a protocol then ...

i mean this:

class MyView: UIView {
    optional part Drawing
}

part Drawing of MyView { // **** forgot to include this into target
    override func drawRect(...) {
        .....
    }
}

the app compiles but doesn't work correctly.

That example doesn’t make any sense. You wouldn’t override a drawRect function in the shared file because that function is overriding a platform-specific function. This is exactly the use case I gave an example for. What you would do instead is override the platform-specific draw function in a platform-specific file and then redirect to a shared implementation that has a cross-platform interface.

In general I would say you shouldn’t implement a protocol method for a protocol not declared in the same file or override a method of a superclass not explicitly mentioned in that file. That’s just a bad practice. Instead you would put that function in the same file as the protocol or superclass is mentioned and then, if needed, redirect to another function in another file. Then if you leave out that file it won’t compile because you’ll be missing a function.

Trust me, this works very well in practice. I never once ran into a bug like that over several years of doing things this way.

···

On Nov 3, 2017, at 10:55 AM, Mike Kluev <mike.kluev@gmail.com> wrote:
On 3 November 2017 at 17:23, Adam Kemp <adam_kemp@apple.com <mailto:adam_kemp@apple.com>> wrote:

1. You end up with a whole section of type aliases at the top of the file,

i'm putting those in a separate Platform.swift file

and as you read the file it’s hard to understand what those types actually
represent.

in the example above "View" - a very similar concept between macOS and
iOS/tvOS/watchOS.
in case of doubts - command click is always at the finger tips.

Which methods are you actually allowed to use? What should code completion
show you?

autocomplete shows the relevant methods on each platform correctly.

2. The type alias is only useful when the naming of the methods/properties

is identical and the sequence of calls you have to make is identical, which
very often isn’t the case. You end up needing conditional compilation
anyway for cases where you only need to make a call on one platform, or
where the method you have to call is named slightly different or takes
different arguments.

if the differences are minute i'm creating my own extension methods to make
the difference non existent.

example: layer is optional on OSX and non optional on iOS. the difference
is minute and i can make this difference non existent, e.g. by:

extension UIView {

    var viewLayer: CALayer? {

        return layer

    }

}

and a similar thing on OSX, and afterwards i have single API on both
platforms with no differences.

You end up needing conditional compilation anyway for cases where you only

need to make a call on one platform, or where the method you have to call
is named slightly different or takes different arguments.

i rarely have to use conditional compilation and even when have to do so i
normally hide it inside the relevant "utility" extensions, from then on i
don't see anything "conditional" within the normal app sources.

Out of the strategies I’ve seen used for cross-platform code this one was
the most frustrating to work with in my experience.

quite the contrary to me - the best possible experience with such an
approach (during many years of experience ftm)

My argument is that there should be no ledger in the first place. IMO you

haven’t made the case for that requirement.

the real-world example would be:

case 1. you have a single page of paper on hands saying: "partial contract
A. continued elsewhere. Blah, blah". you look around and within a multitude
of papers on the table you managed to find another sheet of paper with
"partial contract A. continued elsewhere. Blah, blah". in order to find the
whole thing you will have to look in all papers on your table, then in the
shelve and then in the whole building (module boundaries)

case 2. you have a single page of paper on hands saying: "contract A.
continued with part B elsewhere. blah, blah". you look around and within a
multitude of papers on the table you managed to find another sheet of paper
with: "part B of contract A, blah, blah". at that point you know that you
found everything, don't need to look on the shelve or in the whole building.

case 3. you have a single page of paper on hands saying: "contract A.
continued with part B elsewhere. blah, blah". you look around the whole
building and found no "part B". at this point you know - something is lost,
and act accordingly.

i mean this:

class MyView: UIView {
    optional part Drawing
}

part Drawing of MyView { // **** forgot to include this into target
    override func drawRect(...) {
        .....
    }
}

the app compiles but doesn't work correctly.

That example doesn’t make any sense.

i don't see why (and I am not talking about cross platform code at this
point). i have instances like these implemented via extensions in a day to
day code. the split of the class into files was done merely for organising
purposes, to keep file size manageable.

Mike

···

On 3 November 2017 at 18:08, Adam Kemp <adam_kemp@apple.com> wrote:

1. You end up with a whole section of type aliases at the top of the file,

i'm putting those in a separate Platform.swift file

and as you read the file it’s hard to understand what those types actually represent.

in the example above "View" - a very similar concept between macOS and iOS/tvOS/watchOS.
in case of doubts - command click is always at the finger tips.

Which methods are you actually allowed to use? What should code completion show you?

autocomplete shows the relevant methods on each platform correctly.

2. The type alias is only useful when the naming of the methods/properties is identical and the sequence of calls you have to make is identical, which very often isn’t the case. You end up needing conditional compilation anyway for cases where you only need to make a call on one platform, or where the method you have to call is named slightly different or takes different arguments.

if the differences are minute i'm creating my own extension methods to make the difference non existent.

example: layer is optional on OSX and non optional on iOS. the difference is minute and i can make this difference non existent, e.g. by:

extension UIView {
    var viewLayer: CALayer? {
        return layer
    }
}

and a similar thing on OSX, and afterwards i have single API on both platforms with no differences.

You end up needing conditional compilation anyway for cases where you only need to make a call on one platform, or where the method you have to call is named slightly different or takes different arguments.

i rarely have to use conditional compilation and even when have to do so i normally hide it inside the relevant "utility" extensions, from then on i don't see anything "conditional" within the normal app sources.

Out of the strategies I’ve seen used for cross-platform code this one was the most frustrating to work with in my experience.

quite the contrary to me - the best possible experience with such an approach (during many years of experience ftm)

All I can say is, again, I’ve worked with code that plays these tricks, and I found it much harder to deal with than the code that used partial classes. You’re using all the tricks you can to make it work, but in my experience those tricks add up to a code base that is harder to work with and harder to understand and more brittle than the alternative approach I described. The tricks are part of what makes it confusing. At the risk of sounding presumptuous, I think if you could actually try the other approach (i.e., if we actually had partial classes that work like in C#) you would likely agree. Unfortunately it’s hard to do that comparison when you haven’t tried both approaches in real world situations.

My argument is that there should be no ledger in the first place. IMO you haven’t made the case for that requirement.

the real-world example would be:

case 1. you have a single page of paper on hands saying: "partial contract A. continued elsewhere. Blah, blah". you look around and within a multitude of papers on the table you managed to find another sheet of paper with "partial contract A. continued elsewhere. Blah, blah". in order to find the whole thing you will have to look in all papers on your table, then in the shelve and then in the whole building (module boundaries)

This is not how it works in practice. In practice the files are right next to each other with similar names. It would be more like having “to be continued” at the end of a book sitting right next to another book with the same name plus “Volume 2”. I think you would know where to look.

If you work with people who can’t follow conventions and would try to extend partial classes from random places then I’m sorry. :slight_smile:

You may notice that this has echoes to our earlier conversation where I was on the other side of a very similar disagreement about extensions. I argued that extensions would lead to confusion because they can be thrown anywhere, and someone pointed out that there are file naming conventions for that. I think this is different because extensions are very often included in random files where they are most convenient. They have a different meaning and a different use case that I think encourages their use in files that are not named after the class they’re extending.

Partial classes aren’t intended to be used that way so it really never makes sense to have a partial class implementation in a file that is not dedicated specifically to that partial class implementation. That would raise red flags during code reviews. It’s a very simple rule to follow: if you see “partial class Foo” then the file name better start with “Foo”.

If I thought it made sense to have the compiler somehow enforce the file naming scheme for partial classes I would propose doing that, but it really doesn’t make sense. I don’t think the compiler works that way, and that’s probably for the best.

As for cross-module usages, absolutely under no circumstances do I want this to be supported across modules. I don’t think either of our approaches would work that way so that’s probably not something we need to debate.

i mean this:

class MyView: UIView {
    optional part Drawing
}

part Drawing of MyView { // **** forgot to include this into target
    override func drawRect(...) {
        .....
    }
}

the app compiles but doesn't work correctly.

That example doesn’t make any sense.

i don't see why (and I am not talking about cross platform code at this point). i have instances like these implemented via extensions in a day to day code. the split of the class into files was done merely for organising purposes, to keep file size manageable.

I meant that if you’re doing cross-platform coding using partial classes as I’ve described then the scenario you described is an example of doing things the wrong way. What I’m saying is there is a way of doing things with partial classes in C# where if you follow the pattern then you can’t run into those problems because you will get build errors. If you don’t follow the pattern then bad things could happen, just like with so many other things in programming. There are practices that lead to brittle code and practices that lead to robust code. Using partial classes in C# has best practices that avoid those problems, while at the same time allowing for people to work very efficiently.

Your ledger idea might theoretically prevent some of those bad things from happening, but at the expense of making the whole thing unusable for this use case. That’s not a good trade off.

···

On Nov 3, 2017, at 11:45 AM, Mike Kluev <mike.kluev@gmail.com> wrote:
On 3 November 2017 at 18:08, Adam Kemp <adam_kemp@apple.com <mailto:adam_kemp@apple.com>> wrote:

well, this particular one is not impossible with ledger:

class View: UIView {
   part Feature1 // *** default
   optional part Feature2 // *** the behaviour you describing
}

or even this (if majority agrees this is a better default):

class View: UIView {
   required part Feature1 // *** opt-in
   part Feature2 // *** optional, the behaviour you describing
}

Mike

···

On 3 November 2017 at 21:36, Adam Kemp <adam_kemp@apple.com> wrote:

Your ledger idea might theoretically prevent some of those bad things from
happening, but at the expense of making the whole thing unusable for this
use case. That’s not a good trade off.

This seems naive.

Swift is based on the idea of making it impossible to do things the wrong way, because of Murphy’s Law.

You might get hired to clean up some total crap code that was created in a sweatshop overseas by people who never touched Swift before and only ever wrote in PHP 1.0 before that. I have seen this kind of thing all too often.

Then you have the fact that Swift is used increasingly in school environments, and I sure as heck don’t want some bully kid being able to inject pronz to display on my kid’s viewcontroller just because she wanted to use “parts” or “partials” (or whatever we end up calling it).

Then there are open source projects where all kinds of malicious things could be done with unbounded partiality, and people will hesitate to use this feature without being able to trust its safety.

My point is it’s not always a team of highly trained professional developers working on a project. It could be strangers you will never meet. Someone vindictive or stupid could also do something to deliberately or accidentally break code you worked. And when they blame you, then “those were private methods you used” would no longer be a valid excuse.

I feel much better declaring the parts as a list, and that way you can command-click on them in XCode to jump to those other parts, and the initializer can make sure that all those parts can be found or else block compilation.

Just my 0.02.

J

···

On Nov 3, 2017, at 14:36, Adam Kemp via swift-evolution <swift-evolution@swift.org> wrote:

If you work with people who can’t follow conventions and would try to extend partial classes from random places then I’m sorry. :slight_smile: