On Mon, 21 Nov 2016 at 22:50 John McCall via swift-evolution < swift-evolution@swift.org> wrote:
On Nov 20, 2016, at 6:40 AM, Alan Cabrera <adc@toolazydogs.com> wrote:
On Nov 19, 2016, at 8:57 PM, John McCall <rjmccall@apple.com> wrote:
On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:
On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:
On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:
On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:
On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution < > swift-evolution@swift.org> wrote:
On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:
Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution < > swift-evolution@swift.org> a écrit :
I’m not sure if this was proposed or not; or even if this is a Swift-ly
way of doing things. It would be pretty handy to be able to declare init()
functions in my module to register handlers. It’s a common pattern in
enterprise software.
Currently, I have to generate a lot of boilerplate code to emulate the
behavior. I think it would be cleaner to have these global init()
functions.
I’d rather like a swift attribute equivalent to : __attribute__((
constructor))
It will not force me to call my initializer init, and moreover it will let
me declare multiple functions so I would be able to register multiples
handlers from a single module without having to group all the register call
into a single init() function.
I’m not quite following what “__attribute__((constructor))” means; it
looks like an LLVM implementation bit. Do you mean defining a new Swift
declaration attribute named “constructor”? If so, I *really* like that
idea. I think that the specific attribute name “constructor” may be a bit
confusing though, since it’s not really constructing anything specific.
Maybe “startup” would be a more descriptive attribute name?
@startup
func registerHandlers() {
}
The attribute would also help the compiler and IDEs prevent direct calling
of the startup functions, thus reinforcing/focusing the startup functions’
role as global startup functions. Maybe global teardown functions would be
helpful as well.
I’m going to try goofing around with the idea on my fork.
Some sort of reflective discovery would be better, I think. Eager global
initialization is superficially attractive — what could be simpler than
just running some code at program launch? — but as a program scales up and
gains library dependencies, it very quickly runs into problems. What if an
initializer depends on another already having been run? What if an
initializer needs to be sensitive to the arguments or environment? What if
an initializer need to spawn a thread? What if an initializer needs to do
I/O? What if an initializer fails? Global initialization also has a lot
of the same engineering drawbacks as global state, in that once you've
introduced a dependency on it, it's extremely hard to root that out because
entire APIs get built around the assumption that there's no need for an
explicit initialization/configuration/whatever step. And it's also quite
bad for launch performance — perhaps not important for a server, but
important for pretty much every other kind of program — since every
subsystem eagerly initializes itself whether it's going to be used or not,
and that initialization generally has terrible locality.
Very good points. I recognize the dangers. However.
Don’t these problems already exist given that user code can still execute
at program startup? It cannot be denied that the pattern is used and is
extremely useful though, as you point out above, it should be used
carefully. Thinking on it, there are always pros and cons to most language
features and one relies on best practices to avoid shooting oneself in the
foot. For each of the specters listed above, there are simple accepted
practices that can be adopted to avoid them; most of those practices are
already being employed for other situations.
And the pattern is not just useful in enterprise software. Complex
applications’ app-delegate did-finish-launching methods are chucked full of
hand stitched roll calls to framework initialization code. This needlessly
places a brittle dependency/burden on the application developer in what
should be a simple behind the scenes collaboration.
One could argue that such a thing was never needed before. I would point
to CocoaPods, Carthage, and the other influx of enterprise influenced
tooling and frameworks. Today’s mobile applications are no longer simply
todo apps.
Global init() functions are a clean solution to what engineers are
*already* boiler plating with static singleton code.
No, they aren't a clean solution for the reasons I listed. They may be a
solution you're used to using, but they're not a *clean* solution, and
Swift's line against providing them is for the best.
I'm surprised that you keep talking about enterprise / complex
applications as some sort of argument for them, because those are exactly
the applications where, in my experience, global initializers completely
break down as a reasonable approach. It's the small applications that can
get away with poor software engineering practices, because the accumulated
maintenance/complexity/performance costs are, well, small.
It’s difficult to subscribe to the slippery slope arguments that contain
specters that can still afflict applications without global init
functions. Any feature can be abused and it seems hyperbolic to provide
arguments that seems to ascribe the above problems as an *inevitability* solely
afflicting global init functions. My and others’ experience with them has
been very different from yours.
With that said, I took some time to re-read your reply, after my afternoon
nap. I really like the idea of some kind of reflective discovery. How
would that work? Maybe having a special @tag attribute that can be
searched at runtime?
There was another thread that mentioned this idea recently, but it would
be reasonable to provide some way to get a P.Type for every type in the
program that conforms to a protocol P.
Great, are there any keywords I can use to search for this thread?
This would be opt-in at the protocol level, because we wouldn't want to be
prevented from e.g. stripping an unused type from the program just because
it implemented Equatable. There are some other complexities here, but
that's the basic idea, and it's totally reasonable to support.
Does the other thread go into detail about this? I’m not sure that I
follow why it should be opt-in as opposed to simply searching for all
implementations of a specific protocol.
We would want this to be opt-in on the protocol because it would inhibit
removing a conforming type from the program. The compiler can ordinarily
remove types that you never directly use, and that's an important
optimization when e.g. linking in large libraries that you only use a small
part of. We wouldn't want to lose the ability to do that just because a
type implemented Equatable or Collection, because a lot of types implement
those protocols, and the ability to iterate all those types is probably not
very useful — certainly it isn't useful enough to justify losing that
optimization. In contrast, when there's a protocol that is useful to
iterate over all the conformances of, like a Deserializable protocol that
registers types with a deserialization engine, you always certainly know
that when you're defining the protocol.
John.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution