[swift-evolution] [pitch] composition as part of the language

Nevin Brackett-Rozinsky nevin.brackettrozinsky at gmail.com
Fri Jun 23 08:29:57 CDT 2017


This sounds similar to automatic protocol forward, have you looked into
prior discussions on that topic here?

Nevin


On Thu, Jun 22, 2017 at 10:56 PM, Jay Abbott via swift-evolution <
swift-evolution at swift.org> wrote:

> Let's take a quick look at how we can achieve very simple compile-time
> composition in Swift today.
>
> I want to define a `Doorman` behaviour, and I want to compose it from
> other behaviours that are shared by some of my other staff types:
>
> ```swift
> protocol Greeter {
>     func greet()
> }
> protocol Fareweller {
>     func farewell()
> }
> protocol Doorman: Greeter, Fareweller {}
> ```
>
> Great - that's my interface defined, now some implementations that I can
> compose my staff from:
>
> ```swift
> protocol FriendlyGreeter: Greeter {}
> extension FriendlyGreeter {
>     func greet() {
>         print("Hello and welcome")
>     }
> }
>
> protocol FriendlyFareweller: Fareweller {}
> extension FriendlyFareweller {
>     func farewell() {
>         print("I bid thee farewell")
>     }
> }
>
> protocol InsultingGreeter: Greeter {}
> extension InsultingGreeter {
>     func greet() {
>         print("You make me sick")
>     }
> }
>
> protocol InsultingFareweller: Fareweller {}
> extension InsultingFareweller {
>     func farewell() {
>         print("Get lost")
>     }
> }
> ```
>
> Now we have two kinds of `Greeter` and two kinds of `Fareweller` that can
> be used to compose different `Doorman` types (and potentially other staff
> types). Here's two examples:
>
> ```swift
> struct FriendlyDoorman: Doorman, FriendlyGreeter, FriendlyFareweller {}
> struct TrickingDoorman: Doorman, FriendlyGreeter, InsultingFareweller {}
> ```
>
> I can instantiate and make use of these to perform their defined
> behaviours:
>
> ```swift
> let friendly: Doorman = FriendlyDoorman()
> let tricking: Doorman = TrickingDoorman()
> friendly.greet() // Hello and welcome
> friendly.farewell() // I bid thee farewell
> tricking.greet() // Hello and welcome
> tricking.farewell() // Get lost
> ```
>
> It works! But there are some things that could be nicer:
> * I don't really want `***Greeter` or `***Fareweller` to be sub-protocols
> at all, these are supposed to be implementations - the only reason they are
> protocols is so I can extend them with a default implementation and then
> use more than one of them to compose my actual `Doorman` types. This
> clutters up the namespace with unnecessary protocols, that have the same
> interface as their parent.
> * Since the `***Doorman` types need to be instantiable, they are structs.
> I couldn't compose a `LobbyMultiTasker` from a `FriendlyDoorman` and a
> `GrumpyPorter` at compile-time, the same easy way I composed the
> `***Doorman` types. The manual solution would be to add properties for
> `doormanDelegate` and `porterDelegate` and assign appropriate instances
> (run-time composition), then add the boiler-plate to pass on the
> `LobbyMultiTasker` behaviour to these delegates. This is also how the
> compiler could implement this feature.
> * Actually providing the implementations is optional for the protocols
> (the extensions can happily be missing), meaning any error messages will
> appear in the types that fail to fully implement the protocols, instead of
> here in my `***Greeter` implementation.
>
> So I'd like to discuss the possibility of a new category of types called
> `component`, to allow compile-time composition; composition as part of the
> language. The `***Greeter` and `***Fareweller` might look like this:
>
> ```swift
> component FriendlyGreeter: Greeter {
>     func greet() {
>         print("Hello and welcome")
>     }
> }
>
> component FriendlyFareweller: Fareweller {
>     func farewell() {
>         print("I bid thee farewell")
>     }
> }
>
> component InsultingGreeter: Greeter {
>     func greet() {
>         print("You make me sick")
>     }
> }
>
> component InsultingFareweller: Fareweller {
>     func farewell() {
>         print("Get lost")
>     }
> }
> ```
>
> And the `TrickingDoorman` might look like this:
>
> ```swift
> component TrickingDoorman: Doorman⎄(FriendlyGreeter, InsultingFareweller) {
>     // optionally override any Doorman-defined functions
> }
>
>
> ```
>
> Here's how I think they would work:
>
> * Components must conform to at least one protocol.
> * Components must provide an implementation for all the things from the
> protocols - no part of it is 'abstract' - and they can't provide extra
> things (although it might be useful to allow some kind of config-values,
> but I'd say initially keep it simple).
> * Components can be composed of other components and override some/all of
> the borrowed behaviour. This should provide well defined
> multiple-inheritence semantics (not sure of details), some syntax would be
> required to allow the compiler to totally flatten the component, selecting
> which "parent" implementations to use if needed to satisfy all the
> protocols. Only the functions from the explicit protocols are pulled in,
> not everything implemented in the "parent" components.
> * Components can be instantiated and have value-semantics, like structs
> (they are basically structs with extra features/checks). They can therefore
> be used at compile-time but also at run-time for example `delegate =
> MyComponent()` - so composed behaviour can be either static or dynamic,
> also existing protocol-based `delegate` variables can have a component
> instance assigned.
> * Classes, structs, and enums can NOT override functions implemented in a
> component, when they compose themselves from those components. To do this,
> create a sub-component and override the method, then compose your type from
> that instead.
>
> Other benefits:
> * I think this would encourage more single-responsibility by default.
> Because when designing a protocol, people tend to forget composition.
> Enforcing must-implement-everything would remind/encourage API designers to
> split protocols up, especially if they want to provide a default
> implementation for some but not all of the functions.
> * Tidier code, with much clearer intention (component vs extension in
> particular).
> * Easy re-use without having pass-through boilerplate code.
> * Brings well-defined-ness to default implementations, which can sometimes
> be unclear whether it's an actual default implementation or an empty
> placeholder
>
>  I haven't thought of everything here, obviously - and I'm tired, so
> please poke holes and supply constructive corrections :)
>
> Any suggestions for the "composed-of" syntax for when a type wants to
> utilise a component would be welcome. I used `Doorman⎄(FriendlyGreeter,
> InsultingFareweller)` in the example above, where ⎄ is the composition
> symbol that I just discovered, but this is just intended to be a
> placeholder.
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170623/dd9d17af/attachment.html>


More information about the swift-evolution mailing list