> On Nov 8, 2017, at 5:27 PM, Johannes Weiß via swift-dev <swift-dev@swift.org> 
> wrote:
> 
> Hi Daniel,
> 
>> On 2 Nov 2017, at 8:15 pm, Daniel Dunbar <daniel_dun...@apple.com> wrote:
>> 
>> My personal preference is to:
>> 1. Do nothing for now, but encourage publishing standardized protocols to 
>> solve this need.
>> 2. Hope for a future with WMO+LTO magic which recovers the performance, for 
>> the case where the entire application ends up using one implementation.
> 
> Hmm, but that'll only work if we get 'whole product optimisation', right?

yes.

Even when we have cross-module optimizations (which would be comparable to 
thin-lto) we could not do that optimization.

> If we still compile one module at the time I don't think the compiler will be 
> able to figure out that there's just one implementation of that protocol in 
> the whole program.

exactly

> In fact it can't as that module might be linked into different programs and 
> one of those programs might have a second implementation of that protocol. 
> This is extremely likely as the 'test program' might have a mock/fake or some 
> special implementation. Did I misunderstand you here?
> 
> 
>> You can manage some of the “dependency injection” part by making the package 
>> which exports the common protocol also export a global variable for the 
>> concrete implementation in use (with setters). That could just be a 
>> “pattern” people follow. This wouldn’t be particularly pretty, but it would 
>> mean that intermediate packages could avoid declaring a concrete dependency 
>> on any one implementation, and leave it up to clients to pick.
> 
> hmm, two questions:
> - what would the type of that global variable be? All libraries in the 
> program will need to know and agree on that type
> - sounds like ThreadSanitizer would trap here unless we synchronise it which 
> would make it slow again
> 
> 
> -- Johannes
> 
>> - Daniel
>> 
>>> On Nov 2, 2017, at 5:57 PM, Johannes Weiß via swift-dev 
>>> <swift-dev@swift.org> wrote:
>>> 
>>> Hi swift-dev,
>>> 
>>> I talked to a few people about this problem and we agreed that it is a 
>>> problem and that it needs to be discussed. I didn't quite know where it 
>>> would fit best but let's go with swift-dev, please feel free to tell to 
>>> post it elsewhere if necessary. And apologies for the long mail, couldn't 
>>> come up with a sensible tl;dr...
>>> 
>>> Let me briefly introduce the problem what for the lack of a better name I 
>>> call 'signature package' or 'Service Provider Interface' (SPI) as some 
>>> people from the Java community seem to be calling it 
>>> (https://en.wikipedia.org/wiki/Service_provider_interface). For the rest of 
>>> this email I'll use the term SPI.
>>> 
>>> In a large ecosystem there is a few pieces that many libraries will depend 
>>> on and yet it seems pretty much impossible to standardise exactly one 
>>> implementation. Logging is a very good example as many people have 
>>> different ideas about how logging should and should not work. At the moment 
>>> I guess your best bet is to use your preferred logging API and hope that 
>>> all your other dependencies use the same one. If not you'll likely run into 
>>> annoying problems (different sub-systems logging to different places or 
>>> worse).
>>> 
>>> Also, in a world where some dependencies might be closed source this is an 
>>> even bigger problem as clearly no open-source framework will depend on 
>>> something that's not open-source.
>>> 
>>> 
>>> In Java the way seems to be to standardise on some logging interface (read 
>>> `protocol`) with different implementations. For logging that'd probably be 
>>> SLF4J [4]. In Swift:
>>> 
>>>  let logger: LoggerProtocol = MyFavouriteLoggingFramework(configuration)
>>> 
>>> where `LoggerProtocol` comes from some SPI package and 
>>> `MyFavouriteLoggingFramework` is basically what the name says. And as a 
>>> general practise, everybody would only use `LoggerProtocol`. Then tomorrow 
>>> when I'll change my mind replacing `MyFavouriteLoggingFramework` by 
>>> `BetterFasterLoggingFramework` does the job. With 'dependency injection' 
>>> this 'logger' is handed through the whole program and there's a good chance 
>>> of it all working out. The benefits are that everybody just needs to agree 
>>> on a `protocol` instead of an implementation. 👍
>>> 
>>> In Swift the downside is that this means we're now getting a virtual 
>>> dispatch and the existential everywhere (which in Java will be optimised 
>>> away by the JIT). That might not be a huge problem but it might undermine 
>>> 'CrazyFastLoggingFramework's adoption as we always pay overhead.
>>> 
>>> I don't think this problem can be elegantly solved today. What I could make 
>>> work today (and maybe we could add language/SwiftPM support to facilitate 
>>> it) is this (⚠️, it's ugly)
>>> 
>>> - one SwiftPM package defines the SPI only, the only thing it exports is a 
>>> `public protocol` called say `_spi_Logger`, no implementation
>>> - every implementation of that SPI defines a `public struct Logger: 
>>> _spi_Logger` (yes, they all share the _same_ name)
>>> - every package that wants to log contains
>>> 
>>>  #if USE_FOO_LOGGER
>>>      import FooLogger
>>>  #elif USE_BAR_LOGGER
>>>      import BarLogger
>>>  #else
>>>      import BuzLogger
>>>  #endif
>>> 
>>> where 'BuzLogger' is the preferred logging system of this package but if 
>>> either `USE_FOO_LOGGER` or `USE_BAR_LOGGER` was defined this package is 
>>> happy to use those as well.
>>> - `Logger` is always used as the type, it might be provided by different 
>>> packages though
>>> - in Package.swift of said package we'll need to define something like this:
>>> 
>>>   func loggingDependency() -> Package.Dependency {
>>>   #if USE_FOO_LOGGER
>>>       return .package(url: "github.com/...../foo.git", ...)
>>>   #elif USE_BAR_LOGGER
>>>       return ...
>>>   #else
>>>       return .package(url: "github.com/...../buz.git", ...)
>>>   #endif
>>>   }
>>> 
>>>    func loggingDependencyTarget() -> Target.Dependency {
>>>   #if USE_FOO_LOGGER
>>>       return "foo"
>>>   #elif USE_BAR_LOGGER
>>>       return "bar"
>>>   #else
>>>       return "buz"
>>>   #endif
>>>   }
>>> - in the dependencies array of Package.swift we'll then use 
>>> `loggingDependency()` and in the target we use `loggingDependencyTarget()` 
>>> instead of the concrete one
>>> 
>>> Yes, it's awful but even in a world with different opinions about the 
>>> implementation of a logger, we can make the program work.
>>> In the happy case where application and all dependency agree that 
>>> 'AwesomeLogging' is the best framework we can just type `swift build` and 
>>> everything works. In the case where some dependencies think 
>>> 'AwesomeLogging' is the best but others prefer 'BestEverLogging' we can 
>>> force the whole application into one using `swift build -Xswiftc 
>>> -DUSE_AWESOME_LOGGING` or `swift build -Xswiftc -DUSE_BEST_EVER_LOGGING`.
>>> 
>>> 
>>> Wrapping up, I can see a few different options:
>>> 
>>> 1) do nothing and live with the situation (no Swift/SwiftPM changes 
>>> required)
>>> 2) advertise something similar to what I propose above (no Swift/SwiftPM 
>>> changes required)
>>> 3) do what Java does but optimise the existential away at compile time (if 
>>> the compiler can prove there's actually only one type that implements that 
>>> protocol)
>>> 4) teach SwiftPM about those SPI packages and make everything work, maybe 
>>> by textually replacing the import statements in the  source?
>>> 5) do what Haskell did and retrofit a module system that can support this
>>> 6) have 'special' `specialized protocol` for which a concrete 
>>> implementation needs to be selected by the primary source
>>> 7) something I haven't thought of
>>> 
>>> Btw, both Haskell (with the new 'backpack' [1, 2]) and ML have 'signatures' 
>>> to solve this problem. A signature is basically an SPI. For an example see 
>>> the backpack-str [3] module in Haskell which defines the signature 
>>> (str-sig) and a bunch of different implementations for that signature 
>>> (str-bytestring, str-string, str-foundation, str-text, ...).
>>> 
>>> Let me know what you think!
>>> 
>>> [1]: https://plv.mpi-sws.org/backpack/
>>> [2]: https://ghc.haskell.org/trac/ghc/wiki/Backpack
>>> [3]: https://github.com/haskell-backpack/backpack-str
>>> [4]: https://www.slf4j.org
>>> 
>>> -- Johannes
>>> PS: I attached a tar ball which contains the following 6 SwiftPM packages 
>>> that are created like I describe above:
>>> 
>>> - app,      the main application, prefers the 'foo' logging library
>>> - somelibA, some library which logs and prefers the 'foo' logging library
>>> - somelibB, some other library which prefers the 'bar' logging library
>>> - foo,      the 'foo' logging library
>>> - bar,      the 'bar' logging library
>>> - spi,      the logging SPI
>>> 
>>> The dependency default graph looks like this:
>>>    +- somelibA ---+ foo
>>>   /              /      \
>>> app +--------------/        +-- spi
>>>   \                     /
>>>    +- somelibB ---- bar
>>> 
>>> that looks all good, except that 'foo' and 'bar' are two logging libraries 
>>> 🙈. In other words, we're in the unhappy case, therefore just typing `swift 
>>> build` gives this:
>>> 
>>> --- SNIP ---
>>> -1- johannes:~/devel/swift-spi-demo/app
>>> $ swift build
>>> Compile Swift Module 'app' (1 sources)
>>> /Users/johannes/devel/swift-spi-demo/app/Sources/app/main.swift:14:23: 
>>> error: cannot convert value of type 'Logger' to expected argument type 
>>> 'Logger'
>>> somelibB_func(logger: logger)
>>>                    ^~~~~~
>>> error: terminated(1): 
>>> /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-build-tool
>>>  -f /Users/johannes/devel/swift-spi-demo/app/.build/debug.yaml main
>>> --- SNAP ---
>>> 
>>> because there's two `Logger` types. But selecting `foo` gives (note that 
>>> all lines start with 'Foo:'):
>>> 
>>> --- SNIP ---
>>> $ swift build -Xswiftc -DUSE_FOO
>>> Compile Swift Module 'spi' (1 sources)
>>> Compile Swift Module 'foo' (1 sources)
>>> Compile Swift Module 'bar' (1 sources)
>>> Compile Swift Module 'somelibB' (1 sources)
>>> Compile Swift Module 'somelibA' (1 sources)
>>> Compile Swift Module 'app' (1 sources)
>>> Linking ./.build/x86_64-apple-macosx10.10/debug/app
>>> $ ./.build/x86_64-apple-macosx10.10/debug/app
>>> Foo: info: hello from the app
>>> Foo: info: hello from somelibA
>>> Foo: info: hello from somelibB
>>> Foo: info: hello from somelibA
>>> Foo: info: hello from somelibB
>>> --- SNAP ---
>>> 
>>> and for 'bar' (note that all lines start with 'Bar:')
>>> 
>>> --- SNIP ---
>>> $ swift build -Xswiftc -DUSE_BAR
>>> Compile Swift Module 'spi' (1 sources)
>>> Compile Swift Module 'foo' (1 sources)
>>> Compile Swift Module 'bar' (1 sources)
>>> Compile Swift Module 'somelibA' (1 sources)
>>> Compile Swift Module 'somelibB' (1 sources)
>>> Compile Swift Module 'app' (1 sources)
>>> Linking ./.build/x86_64-apple-macosx10.10/debug/app
>>> $ ./.build/x86_64-apple-macosx10.10/debug/app
>>> Bar: info: hello from the app
>>> Bar: info: hello from somelibA
>>> Bar: info: hello from somelibB
>>> Bar: info: hello from somelibA
>>> Bar: info: hello from somelibB
>>> --- SNAP ---
>>> 
>>> <swift-spi-demo.tar.gz>
>>> 
>>> 
>>> _______________________________________________
>>> swift-dev mailing list
>>> swift-dev@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-dev
>> 
> 
> _______________________________________________
> swift-dev mailing list
> swift-dev@swift.org
> https://lists.swift.org/mailman/listinfo/swift-dev

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

Reply via email to