[Draft] Allow multiple conformances to the same protocol


(Anton Zhilin) #1

==Motivation==

protocol From {
    associatedtype FromType
    init(_ value: FromType)
}

The problem is, one type cannot implement multiple From "conversions".

==Proposed solution==

Allow specifying all associated types using generic syntax.

extension Int : From<Float> { }
extension Int : From<Double> { }

This is only allowed in conformance declarations.

==Future directions==

We can replace all *Convertible protocols with From and Into, which will be
defined similarly to Rust.

- Anton


Explicit ordering of multiple conditional conformances
(Jordan Rose) #2

Associated types aren't generic parameters; the whole point is that they're requirements, just like the other declarations in a protocol.

You might be trying to invent generic protocols instead, which (IIRC) aren't inherently a bad thing. But I think this needs a lot more fleshing out before it can really be discussed—it's hard to know how you can and can't use these things, and how they might be implemented.

Best,
Jordan

···

On Jun 8, 2016, at 12:07, Антон Жилин via swift-evolution <swift-evolution@swift.org> wrote:

==Motivation==

protocol From {
    associatedtype FromType
    init(_ value: FromType)
}

The problem is, one type cannot implement multiple From "conversions".

==Proposed solution==

Allow specifying all associated types using generic syntax.

extension Int : From<Float> { }
extension Int : From<Double> { }

This is only allowed in conformance declarations.

==Future directions==

We can replace all *Convertible protocols with From and Into, which will be defined similarly to Rust.

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


(Austin Zheng) #3

FWIW they're marked as 'unlikely' here:
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols

It would probably be useful to have counterarguments against the points
raised in that document if you want to prepare a proposal.

Austin

···

On Wed, Jun 8, 2016 at 2:32 PM, Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:

Associated types aren't generic parameters; the whole point is that
they're requirements, just like the other declarations in a protocol.

You might be trying to invent generic protocols instead, which (IIRC)
aren't inherently a bad thing. But I think this needs a lot more fleshing
out before it can really be discussed—it's hard to know how you can and
can't use these things, and how they might be implemented.

Best,
Jordan

> On Jun 8, 2016, at 12:07, Антон Жилин via swift-evolution < > swift-evolution@swift.org> wrote:
>
> ==Motivation==
>
> protocol From {
> associatedtype FromType
> init(_ value: FromType)
> }
>
> The problem is, one type cannot implement multiple From "conversions".
>
> ==Proposed solution==
>
> Allow specifying all associated types using generic syntax.
>
> extension Int : From<Float> { }
> extension Int : From<Double> { }
>
> This is only allowed in conformance declarations.
>
> ==Future directions==
>
> We can replace all *Convertible protocols with From and Into, which will
be defined similarly to Rust.
>
> - Anton
> _______________________________________________
> 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


(Vladimir) #4

I like the idea as associatedtype is playing the role of generic type and in extension we conforms to the protocol with some specific generic type as associated type.

I mean the first idea probably could be
protocol From<T> {
      init(_ value: T)
  }
but "protocols do not allow generic parameters; use associated types instead", so it seems natural to express concrete type as associated type for protocol in generic syntax <Type>

Probably alternative syntax could look like:

extension Int : From where .FromType = Float { }

Also, it seems like this proposal could help to solve a problem with the same name of associated type in different protocols:

protocol One {
      associatedtype Element
      func foo(t: Element)
  }

protocol Two {
      associatedtype Element
      func bar(t: Element)
  }

struct OneTwo : One, Two {
     func foo(t: Int) {}
     func bar(t: String) {}
}
// type 'OneTwo' does not conform to protocol 'Two'
// candidate has non-matching type '(t: String) -> ()' [with Element = Element]

So, as I understand, will be possible
struct OneTwo : One, Two<String> {
     func foo(t: Int) {} // OneTwo.Element will be Int
     func bar(t: String) {}
}

···

On 08.06.2016 22:07, Антон Жилин via swift-evolution wrote:

==Motivation==

protocol From {
    associatedtype FromType
    init(_ value: FromType)
}

The problem is, one type cannot implement multiple From "conversions".

==Proposed solution==

Allow specifying all associated types using generic syntax.

extension Int : From<Float> { }
extension Int : From<Double> { }

This is only allowed in conformance declarations.

==Future directions==

We can replace all *Convertible protocols with From and Into, which will be
defined similarly to Rust.

- Anton

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


(Brent Royal-Gordon) #5

FWIW they're marked as 'unlikely' here: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols

It would probably be useful to have counterarguments against the points raised in that document if you want to prepare a proposal.

Here's my counterargument.

  * * *

Firstly, I think they're underestimating the feature's utility. Generic protocols (real generic protocols, not Sequence<Element>) are already needed to make several existing or likely future features work better. For instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch` statement, you do it in an ad hoc, almost Objective-C-like way: You define a free `~=` operator and the compiler resolves the overloads to magically find and use it. There is no way to constrain a generic parameter to "only types that can pattern match against type X", which seems like a pretty useful thing to offer. For instance, in the past people have suggested some sort of expression-based switch alternative. The lack of a pattern matching protocol makes this impossible to implement in either the standard library or your own code.

If we had generic protocols, we could define a protocol for this matching operator and fix the issue:

  protocol Matchable<MatchingValue> {
    func ~= (pattern: Self, value: MatchingValue) -> Bool
  }
  
  protocol Equatable: Matchable<Self> {
    func == (lhs: Self, rhs: Self) -> Bool
  }
  func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
    return lhs == rhs
  }
  
  extension Range: Equatable, Matchable<Bound> {}
  func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
    return pattern.lowerBound <= value && value < pattern.upperBound
  }

Then you could write, for instance, a PatternDictionary which took patterns instead of keys and, when subscripted, matched the key against each pattern until it found a matching one, then returned the corresponding value.

* String interpolation

Currently, StringInterpolationConvertible only offers an `init<T>(stringInterpolationSegment: T)` initializer. That means you absolutely *must* permit any type to be interpolated into your type's string literals. This blocks certain important use cases, like a `LocalizedString` type which requires all strings it interacts with to pass through a localization API, from being statically checked. It also would normally require any type-specific behavior to be performed through runtime tests, but just as in `~=`, the Swift compiler applies compile-time magic to escape this restriction—you can write an `init(stringInterpolationSegment:)` with a concrete type, and that will be preferred over the generic one.

In theory, it should be possible in current Swift to redefine StringInterpolationConvertible to allow you to restrict the interpolatable values by doing something like this:

  protocol StringInterpolationConvertible {
    associatedtype Interpolatable = Any
    init(stringInterpolation: Self...)
    init(stringInterpolationSegment expr: Interpolatable)
  }

(This is no longer generic because I believe Interpolatable would have to be somehow constrained to only protocol types to make that work. But you get the idea.)

However, in many uses, developers will want to support interpolation of many custom types which do not share a common supertype. For instance, LocalizedString might want to support interpolation of any LocalizedString, Date, Integer, or FloatingPoint number. However, since Integer and FloatingPoint are protocols, you cannot use an extension to make them retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible like this:

  protocol StringInterpolationConvertible<Interpolatable> {
    init(stringInterpolation: Self...)
    init(stringInterpolationSegment expr: Interpolatable)
  }

And then say:

  extension LocalizedString: StringInterpolationConvertible<LocalizedString>, StringInterpolationConvertible<Integer>, StringInterpolationConvertible<FloatingPoint> {
    init(stringInterpolationSegment expr: LocalizedString) {
      self.init()
      self.components = expr.components
    }
    init(stringInterpolationSegment expr: Integer) {
      self.init()
      self.components.append(.integer(expr))
    }
    init(stringInterpolationSegment expr: FloatingPoint) {
      self.components.append(.floatingPoint(expr))
    }
    init(stringInterpolation strings: LocalizedString...) {
      self.init()
      self.components = strings.map { $0.components }.reduce([], combine: +)
    }
  }

This example shows an interesting wrinkle: A generic protocol may have requirements which don't use any of the generic types, so that each of the multiple conformances will require members with identical signatures. When this happens, Swift must only allow the member to be implemented once, with that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit at least value types to form subtype-supertype relationships with each other. A protocol would be a sensible way to express this behavior:

  protocol Upcastable {
    associatedtype Supertype
    
    init?(attemptingCastFrom value: Supertype)
    func casting() -> Supertype
  }

However, this would require a type to have only one supertype, which isn't necessarily appropriate. For instance, we might want a UInt8 to be a subtype of both Int16 and UInt16. For that to work, Upcastable would have to be generic:

  protocol Upcastable<Supertype> {
    init?(attemptingCastFrom value: Supertype)
    func casting() -> Supertype
  }
  
  extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

  * * *

Secondly, I think the concerns about people trying to use Sequence as a generic protocol aren't that big a deal. To put it simply: Sequence is *not* a generic protocol. The Swift team controls the definition of Sequence, and we define it to not be generic. If people complain, we explain that generic protocols don't actually do the right thing for this and that they should use existentials instead. We put it in a FAQ. It's just not that big a deal.

The real concern is not that people will try to use Sequence as a generic protocol, but that they will try to inappropriately make their own protocols generic. I see this as a more minor issue, but if we're worried about it, we can address it by changing the mental model to one which doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols", it could instead be thought of as "associated type overloading": a particular associated type can be overloaded, and you can use a `where` clause to select a particular overload. This would have a different syntax but handle the same use cases.

For instance, rather than saying this:

  protocol Matchable<MatchingValue> {
    func ~= (pattern: Self, value: MatchingValue) -> Bool
  }
  
  protocol Equatable: Matchable<Self> {
    func == (lhs: Self, rhs: Self) -> Bool
  }
  func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
    return lhs == rhs
  }
  
  extension Range: Equatable, Matchable<Bound> {}
  func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
    return pattern.lowerBound <= value && value < pattern.upperBound
  }
  
  struct PatternDictionary<Matching, Value>: DictionaryLiteralConvertible {
    typealias Key = Matchable<Matching>
    typealias Value = OutValue
    
    var patterns: DictionaryLiteral<Key, Value>
    init(dictionaryLiteral pairs: (Key, Value)...) { patterns = DictionaryLiteral(pairs) }
    
    subscript(matchingValue: Matching) -> Value? {
      for (pattern, value) in patterns {
        if pattern ~= matchingValue {
          return value
        }
      }
      return nil
    }
  }

You could instead say:

  protocol Matchable {
    @overloadable associatedtype MatchingValue
    func ~= (pattern: Self, value: MatchingValue) -> Bool
  }
  
  protocol Equatable: Matchable where MatchingValue |= Self {
    func == (lhs: Self, rhs: Self) -> Bool
  }
  func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
    return lhs == rhs
  }
  
  extension Range: Equatable, Matchable {
    typealias MatchingValue |= Bound
  }
  func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
    return pattern.lowerBound <= value && value < pattern.upperBound
  }

  struct PatternDictionary<Matching, Value>: DictionaryLiteralConvertible {
    typealias Key = Any<Matchable where .MatchingValue & Matching>
    typealias Value = Value
    
    var patterns: DictionaryLiteral<Key, Value>
    init(dictionaryLiteral pairs: (Key, Value)...) { patterns = DictionaryLiteral(pairs) }
    
    subscript(matchingValue: Matching) -> Value? {
      for (pattern, value) in patterns {
        if pattern ~= matchingValue {
          return value
        }
      }
      return nil
    }
  }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It does have the syntax of one, but there's a separate overload for each type, so I don't think it really acts like one.)

This is very nearly the same feature, but presented with different syntax—effectively with a different metaphor. That should prevent it from being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in theory, there's no reason you couldn't conform to a protocol with an `@overloadable associatedtype` and define zero types. On the other hand, that's not necessarily *wrong*, and might even be useful in some cases.)

···

--
Brent Royal-Gordon
Architechies


(Anton Zhilin) #6

A problem with my solution is that there is a conflict between
associatedtype declarations inherited from From<Int> and From<Double>.
Or in your example, associatedtype Element = Int and associatedtype
Element = String are in conflict.
Another example:

protocol Computer {
    associatedtype Internal
    mutable func prepare() -> Internal
    mutable func compute(input: Internal)
}

extension MyType : Computer<Int> { }
extension MyType : Computer<Double> { }

func test<T: Computer>(input: inout T) {
    let internal: T.Internal = input.prepare()
    input.compute(internal)
}

What is T.Internal , Int or Double? I showed the problem very eplicitly,
but it would exist hidden in a much greater number of cases.

It's not that such resolution is impossible, but solution of Chris does not
have this problem at all: generic types do not create associated type
requirements.
In this case, there is no ambiguity:

protocol Computer<Internal> {
    mutable func prepare() -> Internal
    mutable func compute(input: Internal)
}

extension MyType : Computer<Int> { }
extension MyType : Computer<Double> { }

func test<I, T: Computer<I>>(input: inout T) {
    let internal: I = input.prepare()
    input.compute(internal)
}

test(MyType() as Computer<Int>) // no ambiguity
test(MyType() as Computer<Double>) // no ambiguity

- Anton

···

2016-06-09 17:25 GMT+03:00 Vladimir.S <svabox@gmail.com>:

I like the idea as associatedtype is playing the role of generic type and
in extension we conforms to the protocol with some specific generic type as
associated type.

I mean the first idea probably could be
protocol From<T> {
     init(_ value: T)
}
but "protocols do not allow generic parameters; use associated types
instead", so it seems natural to express concrete type as associated type
for protocol in generic syntax <Type>

Probably alternative syntax could look like:

extension Int : From where .FromType = Float { }

Also, it seems like this proposal could help to solve a problem with the
same name of associated type in different protocols:

protocol One {
     associatedtype Element
     func foo(t: Element)
}

protocol Two {
     associatedtype Element
     func bar(t: Element)
}

struct OneTwo : One, Two {
    func foo(t: Int) {}
    func bar(t: String) {}
}
// type 'OneTwo' does not conform to protocol 'Two'
// candidate has non-matching type '(t: String) -> ()' [with Element =
Element]

So, as I understand, will be possible
struct OneTwo : One, Two<String> {
    func foo(t: Int) {} // OneTwo.Element will be Int
    func bar(t: String) {}

}

On 08.06.2016 22:07, Антон Жилин via swift-evolution wrote:

==Motivation==

protocol From {
    associatedtype FromType
    init(_ value: FromType)
}

The problem is, one type cannot implement multiple From "conversions".

==Proposed solution==

Allow specifying all associated types using generic syntax.

extension Int : From<Float> { }
extension Int : From<Double> { }

This is only allowed in conformance declarations.

==Future directions==

We can replace all *Convertible protocols with From and Into, which will
be
defined similarly to Rust.

- Anton

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


(Ben Rimmington) #7

Brent Royal-Gordon wrote:

Currently, StringInterpolationConvertible only offers an
`init<T>(stringInterpolationSegment: T)` initializer.
That means you absolutely *must* permit any type to be
interpolated into your type's string literals.

It can be explicitly marked unavailable.

extension LocalizedString : StringInterpolationConvertible {

    @available(*, unavailable)
    public init<T>(stringInterpolationSegment _: T) {
        fatalError()
    }

    public init(stringInterpolationSegment format: String) {
        self.init(format: format, values: [])
    }

    public init(stringInterpolationSegment value: AnyObject) {
        self.init(format: "%@", values: [value])
    }
}

-- Ben


(Anton Zhilin) #8

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

- Anton

···

2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent@architechies.com>:

> FWIW they're marked as 'unlikely' here:
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>
> It would probably be useful to have counterarguments against the points
raised in that document if you want to prepare a proposal.

Here's my counterargument.

        * * *

Firstly, I think they're underestimating the feature's utility. Generic
protocols (real generic protocols, not Sequence<Element>) are already
needed to make several existing or likely future features work better. For
instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch`
statement, you do it in an ad hoc, almost Objective-C-like way: You define
a free `~=` operator and the compiler resolves the overloads to magically
find and use it. There is no way to constrain a generic parameter to "only
types that can pattern match against type X", which seems like a pretty
useful thing to offer. For instance, in the past people have suggested some
sort of expression-based switch alternative. The lack of a pattern matching
protocol makes this impossible to implement in either the standard library
or your own code.

If we had generic protocols, we could define a protocol for this matching
operator and fix the issue:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

Then you could write, for instance, a PatternDictionary which took
patterns instead of keys and, when subscripted, matched the key against
each pattern until it found a matching one, then returned the corresponding
value.

* String interpolation

Currently, StringInterpolationConvertible only offers an
`init<T>(stringInterpolationSegment: T)` initializer. That means you
absolutely *must* permit any type to be interpolated into your type's
string literals. This blocks certain important use cases, like a
`LocalizedString` type which requires all strings it interacts with to pass
through a localization API, from being statically checked. It also would
normally require any type-specific behavior to be performed through runtime
tests, but just as in `~=`, the Swift compiler applies compile-time magic
to escape this restriction—you can write an
`init(stringInterpolationSegment:)` with a concrete type, and that will be
preferred over the generic one.

In theory, it should be possible in current Swift to redefine
StringInterpolationConvertible to allow you to restrict the interpolatable
values by doing something like this:

        protocol StringInterpolationConvertible {
                associatedtype Interpolatable = Any
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

(This is no longer generic because I believe Interpolatable would have to
be somehow constrained to only protocol types to make that work. But you
get the idea.)

However, in many uses, developers will want to support interpolation of
many custom types which do not share a common supertype. For instance,
LocalizedString might want to support interpolation of any LocalizedString,
Date, Integer, or FloatingPoint number. However, since Integer and
FloatingPoint are protocols, you cannot use an extension to make them
retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible
like this:

        protocol StringInterpolationConvertible<Interpolatable> {
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

And then say:

        extension LocalizedString:
StringInterpolationConvertible<LocalizedString>,
StringInterpolationConvertible<Integer>,
StringInterpolationConvertible<FloatingPoint> {
                init(stringInterpolationSegment expr: LocalizedString) {
                        self.init()
                        self.components = expr.components
                }
                init(stringInterpolationSegment expr: Integer) {
                        self.init()
                        self.components.append(.integer(expr))
                }
                init(stringInterpolationSegment expr: FloatingPoint) {
                        self.components.append(.floatingPoint(expr))
                }
                init(stringInterpolation strings: LocalizedString...) {
                        self.init()
                        self.components = strings.map { $0.components
}.reduce([], combine: +)
                }
        }

This example shows an interesting wrinkle: A generic protocol may have
requirements which don't use any of the generic types, so that each of the
multiple conformances will require members with identical signatures. When
this happens, Swift must only allow the member to be implemented once, with
that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit at
least value types to form subtype-supertype relationships with each other.
A protocol would be a sensible way to express this behavior:

        protocol Upcastable {
                associatedtype Supertype

                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

However, this would require a type to have only one supertype, which isn't
necessarily appropriate. For instance, we might want a UInt8 to be a
subtype of both Int16 and UInt16. For that to work, Upcastable would have
to be generic:

        protocol Upcastable<Supertype> {
                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

        extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible
subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

        * * *

Secondly, I think the concerns about people trying to use Sequence as a
generic protocol aren't that big a deal. To put it simply: Sequence is
*not* a generic protocol. The Swift team controls the definition of
Sequence, and we define it to not be generic. If people complain, we
explain that generic protocols don't actually do the right thing for this
and that they should use existentials instead. We put it in a FAQ. It's
just not that big a deal.

The real concern is not that people will try to use Sequence as a generic
protocol, but that they will try to inappropriately make their own
protocols generic. I see this as a more minor issue, but if we're worried
about it, we can address it by changing the mental model to one which
doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols", it
could instead be thought of as "associated type overloading": a particular
associated type can be overloaded, and you can use a `where` clause to
select a particular overload. This would have a different syntax but handle
the same use cases.

For instance, rather than saying this:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Matchable<Matching>
                typealias Value = OutValue

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns
= DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

You could instead say:

        protocol Matchable {
                @overloadable associatedtype MatchingValue
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable where MatchingValue |= Self {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable {
                typealias MatchingValue |= Bound
        }
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Any<Matchable where .MatchingValue &
>
                typealias Value = Value

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns
= DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It does
have the syntax of one, but there's a separate overload for each type, so I
don't think it really acts like one.)

This is very nearly the same feature, but presented with different
syntax—effectively with a different metaphor. That should prevent it from
being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in
theory, there's no reason you couldn't conform to a protocol with an
`@overloadable associatedtype` and define zero types. On the other hand,
that's not necessarily *wrong*, and might even be useful in some cases.)

--
Brent Royal-Gordon
Architechies


(Vladimir) #9

Hmm.. In case we *can* have generic `protocol Computer*<Internal>*{..}` - then yes, it seems like a best solution(generic protocols and using of generic types).

As for hypothetical solution without generic protocol, then we need to separate two `Internal` assotiated types (i.e. separate implementation of Computer(Internal=Int) and Computer(Internal=Double) protocols). I.e. it seems like the same protocol with different assotiated type should be treated as different protocol.

> func test<T: Computer>(input: inout T) {
> let internal: T.Internal = input.prepare()
> input.compute(internal)
> }
>
> What is T.Internal , Int or Double? I showed the problem very eplicitly,
> but it would exist hidden in a much greater number of cases.

As I see the situation: our type implemented two protocols, one Computer with Internal = Int, and second with Internal = Double. So, in case we *can* implement the same protocol with different assotiated types - we *must* have a requirement and the ability to separate these implementation *before* calling this func.
I.e. we should be forced to call `test` simething like this:

test(MyType() as Computer where .Internal = Int)
// just MyType() will produce something like "multiply conformance to Computer with different assotiated types, need explicit cast to one of concrete implementation"

This is just abstract thoughts with abstract syntax, don't know if there is something useful in them at all :slight_smile:

···

On 09.06.2016 18:01, Антон Жилин wrote:

A problem with my solution is that there is a conflict between
associatedtype declarations inherited from From<Int> and From<Double>.
Or in your example, associatedtype Element = Int and associatedtype
Element = String are in conflict.
Another example:

protocol Computer {
    associatedtype Internal
    mutable func prepare() -> Internal
    mutable func compute(input: Internal)
}

extension MyType : Computer<Int> { }
extension MyType : Computer<Double> { }

func test<T: Computer>(input: inout T) {
    let internal: T.Internal = input.prepare()
    input.compute(internal)
}

What is T.Internal , Int or Double? I showed the problem very eplicitly,
but it would exist hidden in a much greater number of cases.

It's not that such resolution is impossible, but solution of Chris does not
have this problem at all: generic types do not create associated type
requirements.
In this case, there is no ambiguity:

protocol Computer<Internal> {
    mutable func prepare() -> Internal
    mutable func compute(input: Internal)
}

extension MyType : Computer<Int> { }
extension MyType : Computer<Double> { }

func test<I, T: Computer<I>>(input: inout T) {
    let internal: I = input.prepare()
    input.compute(internal)
}

test(MyType() as Computer<Int>) // no ambiguity
test(MyType() as Computer<Double>) // no ambiguity

- Anton

2016-06-09 17:25 GMT+03:00 Vladimir.S <svabox@gmail.com
<mailto:svabox@gmail.com>>:

    I like the idea as associatedtype is playing the role of generic type
    and in extension we conforms to the protocol with some specific generic
    type as associated type.

    I mean the first idea probably could be
    protocol From<T> {
         init(_ value: T)
     }
    but "protocols do not allow generic parameters; use associated types
    instead", so it seems natural to express concrete type as associated
    type for protocol in generic syntax <Type>

    Probably alternative syntax could look like:

    extension Int : From where .FromType = Float { }

    Also, it seems like this proposal could help to solve a problem with
    the same name of associated type in different protocols:

    protocol One {
         associatedtype Element
         func foo(t: Element)
     }

    protocol Two {
         associatedtype Element
         func bar(t: Element)
     }

    struct OneTwo : One, Two {
        func foo(t: Int) {}
        func bar(t: String) {}
    }
    // type 'OneTwo' does not conform to protocol 'Two'
    // candidate has non-matching type '(t: String) -> ()' [with Element =
    Element]

    So, as I understand, will be possible
    struct OneTwo : One, Two<String> {
        func foo(t: Int) {} // OneTwo.Element will be Int
        func bar(t: String) {}

    }

    On 08.06.2016 22:07, Антон Жилин via swift-evolution wrote:

        ==Motivation==

        protocol From {
            associatedtype FromType
            init(_ value: FromType)
        }

        The problem is, one type cannot implement multiple From "conversions".

        ==Proposed solution==

        Allow specifying all associated types using generic syntax.

        extension Int : From<Float> { }
        extension Int : From<Double> { }

        This is only allowed in conformance declarations.

        ==Future directions==

        We can replace all *Convertible protocols with From and Into, which
        will be
        defined similarly to Rust.

        - Anton

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


(Xiaodi Wu) #10

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

When you propose this:
Syntax in protocol extensions

protocol MyComparable<T> {
  func < (left: Self, right: T)
}extension MyComparable {
  func > (left: T, right: Self) {
    return right < left
  }
}

Would it be possible for me to write something like:

extension MyComparable<T : SignedNumber> { ... }

- Anton

···

On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution@swift.org> wrote:

2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent@architechies.com>:

> FWIW they're marked as 'unlikely' here:
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>
> It would probably be useful to have counterarguments against the points
raised in that document if you want to prepare a proposal.

Here's my counterargument.

        * * *

Firstly, I think they're underestimating the feature's utility. Generic
protocols (real generic protocols, not Sequence<Element>) are already
needed to make several existing or likely future features work better. For
instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch`
statement, you do it in an ad hoc, almost Objective-C-like way: You define
a free `~=` operator and the compiler resolves the overloads to magically
find and use it. There is no way to constrain a generic parameter to "only
types that can pattern match against type X", which seems like a pretty
useful thing to offer. For instance, in the past people have suggested some
sort of expression-based switch alternative. The lack of a pattern matching
protocol makes this impossible to implement in either the standard library
or your own code.

If we had generic protocols, we could define a protocol for this matching
operator and fix the issue:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

Then you could write, for instance, a PatternDictionary which took
patterns instead of keys and, when subscripted, matched the key against
each pattern until it found a matching one, then returned the corresponding
value.

* String interpolation

Currently, StringInterpolationConvertible only offers an
`init<T>(stringInterpolationSegment: T)` initializer. That means you
absolutely *must* permit any type to be interpolated into your type's
string literals. This blocks certain important use cases, like a
`LocalizedString` type which requires all strings it interacts with to pass
through a localization API, from being statically checked. It also would
normally require any type-specific behavior to be performed through runtime
tests, but just as in `~=`, the Swift compiler applies compile-time magic
to escape this restriction—you can write an
`init(stringInterpolationSegment:)` with a concrete type, and that will be
preferred over the generic one.

In theory, it should be possible in current Swift to redefine
StringInterpolationConvertible to allow you to restrict the interpolatable
values by doing something like this:

        protocol StringInterpolationConvertible {
                associatedtype Interpolatable = Any
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

(This is no longer generic because I believe Interpolatable would have to
be somehow constrained to only protocol types to make that work. But you
get the idea.)

However, in many uses, developers will want to support interpolation of
many custom types which do not share a common supertype. For instance,
LocalizedString might want to support interpolation of any LocalizedString,
Date, Integer, or FloatingPoint number. However, since Integer and
FloatingPoint are protocols, you cannot use an extension to make them
retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible
like this:

        protocol StringInterpolationConvertible<Interpolatable> {
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

And then say:

        extension LocalizedString:
StringInterpolationConvertible<LocalizedString>,
StringInterpolationConvertible<Integer>,
StringInterpolationConvertible<FloatingPoint> {
                init(stringInterpolationSegment expr: LocalizedString) {
                        self.init()
                        self.components = expr.components
                }
                init(stringInterpolationSegment expr: Integer) {
                        self.init()
                        self.components.append(.integer(expr))
                }
                init(stringInterpolationSegment expr: FloatingPoint) {
                        self.components.append(.floatingPoint(expr))
                }
                init(stringInterpolation strings: LocalizedString...) {
                        self.init()
                        self.components = strings.map { $0.components
}.reduce([], combine: +)
                }
        }

This example shows an interesting wrinkle: A generic protocol may have
requirements which don't use any of the generic types, so that each of the
multiple conformances will require members with identical signatures. When
this happens, Swift must only allow the member to be implemented once, with
that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit at
least value types to form subtype-supertype relationships with each other.
A protocol would be a sensible way to express this behavior:

        protocol Upcastable {
                associatedtype Supertype

                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

However, this would require a type to have only one supertype, which
isn't necessarily appropriate. For instance, we might want a UInt8 to be a
subtype of both Int16 and UInt16. For that to work, Upcastable would have
to be generic:

        protocol Upcastable<Supertype> {
                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

        extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible
subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

        * * *

Secondly, I think the concerns about people trying to use Sequence as a
generic protocol aren't that big a deal. To put it simply: Sequence is
*not* a generic protocol. The Swift team controls the definition of
Sequence, and we define it to not be generic. If people complain, we
explain that generic protocols don't actually do the right thing for this
and that they should use existentials instead. We put it in a FAQ. It's
just not that big a deal.

The real concern is not that people will try to use Sequence as a generic
protocol, but that they will try to inappropriately make their own
protocols generic. I see this as a more minor issue, but if we're worried
about it, we can address it by changing the mental model to one which
doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols",
it could instead be thought of as "associated type overloading": a
particular associated type can be overloaded, and you can use a `where`
clause to select a particular overload. This would have a different syntax
but handle the same use cases.

For instance, rather than saying this:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Matchable<Matching>
                typealias Value = OutValue

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns
= DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

You could instead say:

        protocol Matchable {
                @overloadable associatedtype MatchingValue
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable where MatchingValue |= Self {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable {
                typealias MatchingValue |= Bound
        }
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Any<Matchable where .MatchingValue &
>
                typealias Value = Value

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns
= DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It does
have the syntax of one, but there's a separate overload for each type, so I
don't think it really acts like one.)

This is very nearly the same feature, but presented with different
syntax—effectively with a different metaphor. That should prevent it from
being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in
theory, there's no reason you couldn't conform to a protocol with an
`@overloadable associatedtype` and define zero types. On the other hand,
that's not necessarily *wrong*, and might even be useful in some cases.)

--
Brent Royal-Gordon
Architechies

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


(Thorsten Seitz) #11

Looks good to me!

-Thorsten

···

Am 12.06.2016 um 15:01 schrieb Антон Жилин via swift-evolution <swift-evolution@swift.org>:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

- Anton

2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>>:
> FWIW they're marked as 'unlikely' here: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>
> It would probably be useful to have counterarguments against the points raised in that document if you want to prepare a proposal.

Here's my counterargument.

        * * *

Firstly, I think they're underestimating the feature's utility. Generic protocols (real generic protocols, not Sequence<Element>) are already needed to make several existing or likely future features work better. For instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch` statement, you do it in an ad hoc, almost Objective-C-like way: You define a free `~=` operator and the compiler resolves the overloads to magically find and use it. There is no way to constrain a generic parameter to "only types that can pattern match against type X", which seems like a pretty useful thing to offer. For instance, in the past people have suggested some sort of expression-based switch alternative. The lack of a pattern matching protocol makes this impossible to implement in either the standard library or your own code.

If we had generic protocols, we could define a protocol for this matching operator and fix the issue:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
                return pattern.lowerBound <= value && value < pattern.upperBound
        }

Then you could write, for instance, a PatternDictionary which took patterns instead of keys and, when subscripted, matched the key against each pattern until it found a matching one, then returned the corresponding value.

* String interpolation

Currently, StringInterpolationConvertible only offers an `init<T>(stringInterpolationSegment: T)` initializer. That means you absolutely *must* permit any type to be interpolated into your type's string literals. This blocks certain important use cases, like a `LocalizedString` type which requires all strings it interacts with to pass through a localization API, from being statically checked. It also would normally require any type-specific behavior to be performed through runtime tests, but just as in `~=`, the Swift compiler applies compile-time magic to escape this restriction—you can write an `init(stringInterpolationSegment:)` with a concrete type, and that will be preferred over the generic one.

In theory, it should be possible in current Swift to redefine StringInterpolationConvertible to allow you to restrict the interpolatable values by doing something like this:

        protocol StringInterpolationConvertible {
                associatedtype Interpolatable = Any
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

(This is no longer generic because I believe Interpolatable would have to be somehow constrained to only protocol types to make that work. But you get the idea.)

However, in many uses, developers will want to support interpolation of many custom types which do not share a common supertype. For instance, LocalizedString might want to support interpolation of any LocalizedString, Date, Integer, or FloatingPoint number. However, since Integer and FloatingPoint are protocols, you cannot use an extension to make them retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible like this:

        protocol StringInterpolationConvertible<Interpolatable> {
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

And then say:

        extension LocalizedString: StringInterpolationConvertible<LocalizedString>, StringInterpolationConvertible<Integer>, StringInterpolationConvertible<FloatingPoint> {
                init(stringInterpolationSegment expr: LocalizedString) {
                        self.init()
                        self.components = expr.components
                }
                init(stringInterpolationSegment expr: Integer) {
                        self.init()
                        self.components.append(.integer(expr))
                }
                init(stringInterpolationSegment expr: FloatingPoint) {
                        self.components.append(.floatingPoint(expr))
                }
                init(stringInterpolation strings: LocalizedString...) {
                        self.init()
                        self.components = strings.map { $0.components }.reduce([], combine: +)
                }
        }

This example shows an interesting wrinkle: A generic protocol may have requirements which don't use any of the generic types, so that each of the multiple conformances will require members with identical signatures. When this happens, Swift must only allow the member to be implemented once, with that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit at least value types to form subtype-supertype relationships with each other. A protocol would be a sensible way to express this behavior:

        protocol Upcastable {
                associatedtype Supertype

                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

However, this would require a type to have only one supertype, which isn't necessarily appropriate. For instance, we might want a UInt8 to be a subtype of both Int16 and UInt16. For that to work, Upcastable would have to be generic:

        protocol Upcastable<Supertype> {
                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

        extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

        * * *

Secondly, I think the concerns about people trying to use Sequence as a generic protocol aren't that big a deal. To put it simply: Sequence is *not* a generic protocol. The Swift team controls the definition of Sequence, and we define it to not be generic. If people complain, we explain that generic protocols don't actually do the right thing for this and that they should use existentials instead. We put it in a FAQ. It's just not that big a deal.

The real concern is not that people will try to use Sequence as a generic protocol, but that they will try to inappropriately make their own protocols generic. I see this as a more minor issue, but if we're worried about it, we can address it by changing the mental model to one which doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols", it could instead be thought of as "associated type overloading": a particular associated type can be overloaded, and you can use a `where` clause to select a particular overload. This would have a different syntax but handle the same use cases.

For instance, rather than saying this:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
                return pattern.lowerBound <= value && value < pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>: DictionaryLiteralConvertible {
                typealias Key = Matchable<Matching>
                typealias Value = OutValue

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

You could instead say:

        protocol Matchable {
                @overloadable associatedtype MatchingValue
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable where MatchingValue |= Self {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable {
                typealias MatchingValue |= Bound
        }
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
                return pattern.lowerBound <= value && value < pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>: DictionaryLiteralConvertible {
                typealias Key = Any<Matchable where .MatchingValue & Matching>
                typealias Value = Value

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It does have the syntax of one, but there's a separate overload for each type, so I don't think it really acts like one.)

This is very nearly the same feature, but presented with different syntax—effectively with a different metaphor. That should prevent it from being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in theory, there's no reason you couldn't conform to a protocol with an `@overloadable associatedtype` and define zero types. On the other hand, that's not necessarily *wrong*, and might even be useful in some cases.)

--
Brent Royal-Gordon
Architechies

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


(David Sweeris) #12

+1

···

Sent from my iPhone

On Jun 12, 2016, at 08:01, Антон Жилин via swift-evolution <swift-evolution@swift.org> wrote:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

- Anton

2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent@architechies.com>:

> FWIW they're marked as 'unlikely' here: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>
> It would probably be useful to have counterarguments against the points raised in that document if you want to prepare a proposal.

Here's my counterargument.

        * * *

Firstly, I think they're underestimating the feature's utility. Generic protocols (real generic protocols, not Sequence<Element>) are already needed to make several existing or likely future features work better. For instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch` statement, you do it in an ad hoc, almost Objective-C-like way: You define a free `~=` operator and the compiler resolves the overloads to magically find and use it. There is no way to constrain a generic parameter to "only types that can pattern match against type X", which seems like a pretty useful thing to offer. For instance, in the past people have suggested some sort of expression-based switch alternative. The lack of a pattern matching protocol makes this impossible to implement in either the standard library or your own code.

If we had generic protocols, we could define a protocol for this matching operator and fix the issue:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
                return pattern.lowerBound <= value && value < pattern.upperBound
        }

Then you could write, for instance, a PatternDictionary which took patterns instead of keys and, when subscripted, matched the key against each pattern until it found a matching one, then returned the corresponding value.

* String interpolation

Currently, StringInterpolationConvertible only offers an `init<T>(stringInterpolationSegment: T)` initializer. That means you absolutely *must* permit any type to be interpolated into your type's string literals. This blocks certain important use cases, like a `LocalizedString` type which requires all strings it interacts with to pass through a localization API, from being statically checked. It also would normally require any type-specific behavior to be performed through runtime tests, but just as in `~=`, the Swift compiler applies compile-time magic to escape this restriction—you can write an `init(stringInterpolationSegment:)` with a concrete type, and that will be preferred over the generic one.

In theory, it should be possible in current Swift to redefine StringInterpolationConvertible to allow you to restrict the interpolatable values by doing something like this:

        protocol StringInterpolationConvertible {
                associatedtype Interpolatable = Any
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

(This is no longer generic because I believe Interpolatable would have to be somehow constrained to only protocol types to make that work. But you get the idea.)

However, in many uses, developers will want to support interpolation of many custom types which do not share a common supertype. For instance, LocalizedString might want to support interpolation of any LocalizedString, Date, Integer, or FloatingPoint number. However, since Integer and FloatingPoint are protocols, you cannot use an extension to make them retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible like this:

        protocol StringInterpolationConvertible<Interpolatable> {
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

And then say:

        extension LocalizedString: StringInterpolationConvertible<LocalizedString>, StringInterpolationConvertible<Integer>, StringInterpolationConvertible<FloatingPoint> {
                init(stringInterpolationSegment expr: LocalizedString) {
                        self.init()
                        self.components = expr.components
                }
                init(stringInterpolationSegment expr: Integer) {
                        self.init()
                        self.components.append(.integer(expr))
                }
                init(stringInterpolationSegment expr: FloatingPoint) {
                        self.components.append(.floatingPoint(expr))
                }
                init(stringInterpolation strings: LocalizedString...) {
                        self.init()
                        self.components = strings.map { $0.components }.reduce([], combine: +)
                }
        }

This example shows an interesting wrinkle: A generic protocol may have requirements which don't use any of the generic types, so that each of the multiple conformances will require members with identical signatures. When this happens, Swift must only allow the member to be implemented once, with that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit at least value types to form subtype-supertype relationships with each other. A protocol would be a sensible way to express this behavior:

        protocol Upcastable {
                associatedtype Supertype

                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

However, this would require a type to have only one supertype, which isn't necessarily appropriate. For instance, we might want a UInt8 to be a subtype of both Int16 and UInt16. For that to work, Upcastable would have to be generic:

        protocol Upcastable<Supertype> {
                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

        extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

        * * *

Secondly, I think the concerns about people trying to use Sequence as a generic protocol aren't that big a deal. To put it simply: Sequence is *not* a generic protocol. The Swift team controls the definition of Sequence, and we define it to not be generic. If people complain, we explain that generic protocols don't actually do the right thing for this and that they should use existentials instead. We put it in a FAQ. It's just not that big a deal.

The real concern is not that people will try to use Sequence as a generic protocol, but that they will try to inappropriately make their own protocols generic. I see this as a more minor issue, but if we're worried about it, we can address it by changing the mental model to one which doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols", it could instead be thought of as "associated type overloading": a particular associated type can be overloaded, and you can use a `where` clause to select a particular overload. This would have a different syntax but handle the same use cases.

For instance, rather than saying this:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
                return pattern.lowerBound <= value && value < pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>: DictionaryLiteralConvertible {
                typealias Key = Matchable<Matching>
                typealias Value = OutValue

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

You could instead say:

        protocol Matchable {
                @overloadable associatedtype MatchingValue
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable where MatchingValue |= Self {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable {
                typealias MatchingValue |= Bound
        }
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound) -> Bool {
                return pattern.lowerBound <= value && value < pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>: DictionaryLiteralConvertible {
                typealias Key = Any<Matchable where .MatchingValue & Matching>
                typealias Value = Value

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) { patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It does have the syntax of one, but there's a separate overload for each type, so I don't think it really acts like one.)

This is very nearly the same feature, but presented with different syntax—effectively with a different metaphor. That should prevent it from being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in theory, there's no reason you couldn't conform to a protocol with an `@overloadable associatedtype` and define zero types. On the other hand, that's not necessarily *wrong*, and might even be useful in some cases.)

--
Brent Royal-Gordon
Architechies

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


(Anton Zhilin) #13

Yes, everything that works on generic types should work for generic
protocols. I'll add that.

What won't work is declaring that MyComparable : Comparable iff T ==
Self. The same won't work for current non-generic protocols as well.
Although that feature is highly requested, it is discussed in a separate
proposal.

- Anton

···

2016-06-12 21:16 GMT+03:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution@swift.org> > wrote:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

When you propose this:
Syntax in protocol extensions

protocol MyComparable<T> {
  func < (left: Self, right: T)
}extension MyComparable {
  func > (left: T, right: Self) {
    return right < left
  }
}

Would it be possible for me to write something like:

extension MyComparable<T : SignedNumber> { ... }

- Anton

2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent@architechies.com>:

> FWIW they're marked as 'unlikely' here:
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>
> It would probably be useful to have counterarguments against the
points raised in that document if you want to prepare a proposal.

Here's my counterargument.

        * * *

Firstly, I think they're underestimating the feature's utility. Generic
protocols (real generic protocols, not Sequence<Element>) are already
needed to make several existing or likely future features work better. For
instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch`
statement, you do it in an ad hoc, almost Objective-C-like way: You define
a free `~=` operator and the compiler resolves the overloads to magically
find and use it. There is no way to constrain a generic parameter to "only
types that can pattern match against type X", which seems like a pretty
useful thing to offer. For instance, in the past people have suggested some
sort of expression-based switch alternative. The lack of a pattern matching
protocol makes this impossible to implement in either the standard library
or your own code.

If we had generic protocols, we could define a protocol for this
matching operator and fix the issue:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

Then you could write, for instance, a PatternDictionary which took
patterns instead of keys and, when subscripted, matched the key against
each pattern until it found a matching one, then returned the corresponding
value.

* String interpolation

Currently, StringInterpolationConvertible only offers an
`init<T>(stringInterpolationSegment: T)` initializer. That means you
absolutely *must* permit any type to be interpolated into your type's
string literals. This blocks certain important use cases, like a
`LocalizedString` type which requires all strings it interacts with to pass
through a localization API, from being statically checked. It also would
normally require any type-specific behavior to be performed through runtime
tests, but just as in `~=`, the Swift compiler applies compile-time magic
to escape this restriction—you can write an
`init(stringInterpolationSegment:)` with a concrete type, and that will be
preferred over the generic one.

In theory, it should be possible in current Swift to redefine
StringInterpolationConvertible to allow you to restrict the interpolatable
values by doing something like this:

        protocol StringInterpolationConvertible {
                associatedtype Interpolatable = Any
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

(This is no longer generic because I believe Interpolatable would have
to be somehow constrained to only protocol types to make that work. But you
get the idea.)

However, in many uses, developers will want to support interpolation of
many custom types which do not share a common supertype. For instance,
LocalizedString might want to support interpolation of any LocalizedString,
Date, Integer, or FloatingPoint number. However, since Integer and
FloatingPoint are protocols, you cannot use an extension to make them
retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible
like this:

        protocol StringInterpolationConvertible<Interpolatable> {
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

And then say:

        extension LocalizedString:
StringInterpolationConvertible<LocalizedString>,
StringInterpolationConvertible<Integer>,
StringInterpolationConvertible<FloatingPoint> {
                init(stringInterpolationSegment expr: LocalizedString) {
                        self.init()
                        self.components = expr.components
                }
                init(stringInterpolationSegment expr: Integer) {
                        self.init()
                        self.components.append(.integer(expr))
                }
                init(stringInterpolationSegment expr: FloatingPoint) {
                        self.components.append(.floatingPoint(expr))
                }
                init(stringInterpolation strings: LocalizedString...) {
                        self.init()
                        self.components = strings.map { $0.components
}.reduce([], combine: +)
                }
        }

This example shows an interesting wrinkle: A generic protocol may have
requirements which don't use any of the generic types, so that each of the
multiple conformances will require members with identical signatures. When
this happens, Swift must only allow the member to be implemented once, with
that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit at
least value types to form subtype-supertype relationships with each other.
A protocol would be a sensible way to express this behavior:

        protocol Upcastable {
                associatedtype Supertype

                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

However, this would require a type to have only one supertype, which
isn't necessarily appropriate. For instance, we might want a UInt8 to be a
subtype of both Int16 and UInt16. For that to work, Upcastable would have
to be generic:

        protocol Upcastable<Supertype> {
                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

        extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible
subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

        * * *

Secondly, I think the concerns about people trying to use Sequence as a
generic protocol aren't that big a deal. To put it simply: Sequence is
*not* a generic protocol. The Swift team controls the definition of
Sequence, and we define it to not be generic. If people complain, we
explain that generic protocols don't actually do the right thing for this
and that they should use existentials instead. We put it in a FAQ. It's
just not that big a deal.

The real concern is not that people will try to use Sequence as a
generic protocol, but that they will try to inappropriately make their own
protocols generic. I see this as a more minor issue, but if we're worried
about it, we can address it by changing the mental model to one which
doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols",
it could instead be thought of as "associated type overloading": a
particular associated type can be overloaded, and you can use a `where`
clause to select a particular overload. This would have a different syntax
but handle the same use cases.

For instance, rather than saying this:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Matchable<Matching>
                typealias Value = OutValue

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) {
patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

You could instead say:

        protocol Matchable {
                @overloadable associatedtype MatchingValue
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable where MatchingValue |= Self {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable {
                typealias MatchingValue |= Bound
        }
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value: Bound)
-> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Any<Matchable where .MatchingValue &
>
                typealias Value = Value

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) {
patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It does
have the syntax of one, but there's a separate overload for each type, so I
don't think it really acts like one.)

This is very nearly the same feature, but presented with different
syntax—effectively with a different metaphor. That should prevent it from
being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in
theory, there's no reason you couldn't conform to a protocol with an
`@overloadable associatedtype` and define zero types. On the other hand,
that's not necessarily *wrong*, and might even be useful in some cases.)

--
Brent Royal-Gordon
Architechies

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


(Anton Zhilin) #14

When I said that the problem would exist internally, I meant that there
would still be two conflicting associatedtype declarations inside MyType.
This can be perfectly solved by conformance-with-renaming. Example:

extension MyType : Computer {
    from Computer rename Internal to Internal1
    associatedtype Internal1 = Int
}
extension MyType : Computer {
    from Computer rename Internal to Internal2
    associatedtype Internal2 = Double
}

Then MyType should play well both in contexts of T: Computer where
Internal == Int and T: Computer where Internal == Double.

It potentially convers a broad range of conflicts, such as conflicting
functions and conflicting properties.
We could include composition operations of traits in Swift's protocols,
most notably, rename and exclude.
Niall Young <niall@iinet.net.au> is working on this problem, but his
proposal is going to add new separate "trait" declarations for that, a
decision which I don't quite understand.

And still I think I'll propose generic protocols if noone does that before.

- Anton

···

2016-06-09 18:37 GMT+03:00 Vladimir.S <svabox@gmail.com>:

Hmm.. In case we *can* have generic `protocol Computer*<Internal>*{..}` -
then yes, it seems like a best solution(generic protocols and using of
generic types).

As for hypothetical solution without generic protocol, then we need to
separate two `Internal` assotiated types (i.e. separate implementation of
Computer(Internal=Int) and Computer(Internal=Double) protocols). I.e. it
seems like the same protocol with different assotiated type should be
treated as different protocol.

> func test<T: Computer>(input: inout T) {
> let internal: T.Internal = input.prepare()
> input.compute(internal)
> }
>
> What is T.Internal , Int or Double? I showed the problem very eplicitly,
> but it would exist hidden in a much greater number of cases.

As I see the situation: our type implemented two protocols, one Computer
with Internal = Int, and second with Internal = Double. So, in case we
*can* implement the same protocol with different assotiated types - we
*must* have a requirement and the ability to separate these implementation
*before* calling this func.
I.e. we should be forced to call `test` simething like this:

test(MyType() as Computer where .Internal = Int)
// just MyType() will produce something like "multiply conformance to
Computer with different assotiated types, need explicit cast to one of
concrete implementation"

This is just abstract thoughts with abstract syntax, don't know if there
is something useful in them at all :slight_smile:

On 09.06.2016 18:01, Антон Жилин wrote:

A problem with my solution is that there is a conflict between
associatedtype declarations inherited from From<Int> and From<Double>.
Or in your example, associatedtype Element = Int and associatedtype
Element = String are in conflict.
Another example:

protocol Computer {
    associatedtype Internal
    mutable func prepare() -> Internal
    mutable func compute(input: Internal)
}

extension MyType : Computer<Int> { }
extension MyType : Computer<Double> { }

func test<T: Computer>(input: inout T) {
    let internal: T.Internal = input.prepare()
    input.compute(internal)
}

What is T.Internal , Int or Double? I showed the problem very eplicitly,
but it would exist hidden in a much greater number of cases.

It's not that such resolution is impossible, but solution of Chris does
not
have this problem at all: generic types do not create associated type
requirements.
In this case, there is no ambiguity:

protocol Computer<Internal> {
    mutable func prepare() -> Internal
    mutable func compute(input: Internal)
}

extension MyType : Computer<Int> { }
extension MyType : Computer<Double> { }

func test<I, T: Computer<I>>(input: inout T) {
    let internal: I = input.prepare()
    input.compute(internal)
}

test(MyType() as Computer<Int>) // no ambiguity
test(MyType() as Computer<Double>) // no ambiguity

- Anton

2016-06-09 17:25 GMT+03:00 Vladimir.S <svabox@gmail.com
<mailto:svabox@gmail.com>>:

    I like the idea as associatedtype is playing the role of generic type
    and in extension we conforms to the protocol with some specific
generic
    type as associated type.

    I mean the first idea probably could be
    protocol From<T> {
         init(_ value: T)
     }
    but "protocols do not allow generic parameters; use associated types
    instead", so it seems natural to express concrete type as associated
    type for protocol in generic syntax <Type>

    Probably alternative syntax could look like:

    extension Int : From where .FromType = Float { }

    Also, it seems like this proposal could help to solve a problem with
    the same name of associated type in different protocols:

    protocol One {
         associatedtype Element
         func foo(t: Element)
     }

    protocol Two {
         associatedtype Element
         func bar(t: Element)
     }

    struct OneTwo : One, Two {
        func foo(t: Int) {}
        func bar(t: String) {}
    }
    // type 'OneTwo' does not conform to protocol 'Two'
    // candidate has non-matching type '(t: String) -> ()' [with Element =
    Element]

    So, as I understand, will be possible
    struct OneTwo : One, Two<String> {
        func foo(t: Int) {} // OneTwo.Element will be Int
        func bar(t: String) {}

    }

    On 08.06.2016 22:07, Антон Жилин via swift-evolution wrote:

        ==Motivation==

        protocol From {
            associatedtype FromType
            init(_ value: FromType)
        }

        The problem is, one type cannot implement multiple From
"conversions".

        ==Proposed solution==

        Allow specifying all associated types using generic syntax.

        extension Int : From<Float> { }
        extension Int : From<Double> { }

        This is only allowed in conformance declarations.

        ==Future directions==

        We can replace all *Convertible protocols with From and Into,
which
        will be
        defined similarly to Rust.

        - Anton

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


(Dan Appel) #15

Awesome that you guys started this! I've been meaning to pitch it for a
while. Couple notes:

- in the motivation, you're conforming to SequenceType while defining a

From protocol (just a typo I think)

- the proposal should mention Brent's comments since it doesn't have a
rebuttal to the "unlikely" status generic protocols were given in the
generic manifesto. Brent does a great job of doing that
- is there a way for generic protocols to still have associated types? I
think that could have some interesting use cases.

Dan Appel

···

On Sun, Jun 12, 2016 at 11:28 AM Антон Жилин <swift-evolution@swift.org> wrote:

Yes, everything that works on generic types should work for generic
protocols. I'll add that.

What won't work is declaring that MyComparable : Comparable iff T ==
Self. The same won't work for current non-generic protocols as well.
Although that feature is highly requested, it is discussed in a separate
proposal.

- Anton

2016-06-12 21:16 GMT+03:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution@swift.org> >> wrote:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

When you propose this:
Syntax in protocol extensions

protocol MyComparable<T> {
  func < (left: Self, right: T)
}extension MyComparable {
  func > (left: T, right: Self) {
    return right < left
  }
}

Would it be possible for me to write something like:

extension MyComparable<T : SignedNumber> { ... }

- Anton

2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent@architechies.com>:

> FWIW they're marked as 'unlikely' here:
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>
> It would probably be useful to have counterarguments against the
points raised in that document if you want to prepare a proposal.

Here's my counterargument.

        * * *

Firstly, I think they're underestimating the feature's utility. Generic
protocols (real generic protocols, not Sequence<Element>) are already
needed to make several existing or likely future features work better. For
instance:

* Pattern matching

Currently, if you want to customize your type's behavior in a `switch`
statement, you do it in an ad hoc, almost Objective-C-like way: You define
a free `~=` operator and the compiler resolves the overloads to magically
find and use it. There is no way to constrain a generic parameter to "only
types that can pattern match against type X", which seems like a pretty
useful thing to offer. For instance, in the past people have suggested some
sort of expression-based switch alternative. The lack of a pattern matching
protocol makes this impossible to implement in either the standard library
or your own code.

If we had generic protocols, we could define a protocol for this
matching operator and fix the issue:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value:
Bound) -> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

Then you could write, for instance, a PatternDictionary which took
patterns instead of keys and, when subscripted, matched the key against
each pattern until it found a matching one, then returned the corresponding
value.

* String interpolation

Currently, StringInterpolationConvertible only offers an
`init<T>(stringInterpolationSegment: T)` initializer. That means you
absolutely *must* permit any type to be interpolated into your type's
string literals. This blocks certain important use cases, like a
`LocalizedString` type which requires all strings it interacts with to pass
through a localization API, from being statically checked. It also would
normally require any type-specific behavior to be performed through runtime
tests, but just as in `~=`, the Swift compiler applies compile-time magic
to escape this restriction—you can write an
`init(stringInterpolationSegment:)` with a concrete type, and that will be
preferred over the generic one.

In theory, it should be possible in current Swift to redefine
StringInterpolationConvertible to allow you to restrict the interpolatable
values by doing something like this:

        protocol StringInterpolationConvertible {
                associatedtype Interpolatable = Any
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

(This is no longer generic because I believe Interpolatable would have
to be somehow constrained to only protocol types to make that work. But you
get the idea.)

However, in many uses, developers will want to support interpolation of
many custom types which do not share a common supertype. For instance,
LocalizedString might want to support interpolation of any LocalizedString,
Date, Integer, or FloatingPoint number. However, since Integer and
FloatingPoint are protocols, you cannot use an extension to make them
retroactively conform to a common protocol with LocalizedString.

With generic protocols, we could define StringInterpolationConvertible
like this:

        protocol StringInterpolationConvertible<Interpolatable> {
                init(stringInterpolation: Self...)
                init(stringInterpolationSegment expr: Interpolatable)
        }

And then say:

        extension LocalizedString:
StringInterpolationConvertible<LocalizedString>,
StringInterpolationConvertible<Integer>,
StringInterpolationConvertible<FloatingPoint> {
                init(stringInterpolationSegment expr: LocalizedString) {
                        self.init()
                        self.components = expr.components
                }
                init(stringInterpolationSegment expr: Integer) {
                        self.init()
                        self.components.append(.integer(expr))
                }
                init(stringInterpolationSegment expr: FloatingPoint) {
                        self.components.append(.floatingPoint(expr))
                }
                init(stringInterpolation strings: LocalizedString...) {
                        self.init()
                        self.components = strings.map { $0.components
}.reduce([], combine: +)
                }
        }

This example shows an interesting wrinkle: A generic protocol may have
requirements which don't use any of the generic types, so that each of the
multiple conformances will require members with identical signatures. When
this happens, Swift must only allow the member to be implemented once, with
that implementation being shared among all conformances.

* Subtype-supertype relationships

Though not currently implemented, there are long-term plans to permit
at least value types to form subtype-supertype relationships with each
other. A protocol would be a sensible way to express this behavior:

        protocol Upcastable {
                associatedtype Supertype

                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

However, this would require a type to have only one supertype, which
isn't necessarily appropriate. For instance, we might want a UInt8 to be a
subtype of both Int16 and UInt16. For that to work, Upcastable would have
to be generic:

        protocol Upcastable<Supertype> {
                init?(attemptingCastFrom value: Supertype)
                func casting() -> Supertype
        }

        extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }

Without generic protocols, the only way to offer sufficiently flexible
subtyping is to offer it as a one-off, ad-hoc feature with special syntax.

        * * *

Secondly, I think the concerns about people trying to use Sequence as a
generic protocol aren't that big a deal. To put it simply: Sequence is
*not* a generic protocol. The Swift team controls the definition of
Sequence, and we define it to not be generic. If people complain, we
explain that generic protocols don't actually do the right thing for this
and that they should use existentials instead. We put it in a FAQ. It's
just not that big a deal.

The real concern is not that people will try to use Sequence as a
generic protocol, but that they will try to inappropriately make their own
protocols generic. I see this as a more minor issue, but if we're worried
about it, we can address it by changing the mental model to one which
doesn't make it look like a generics feature.

Basically, rather than thinking of this feature as "generic protocols",
it could instead be thought of as "associated type overloading": a
particular associated type can be overloaded, and you can use a `where`
clause to select a particular overload. This would have a different syntax
but handle the same use cases.

For instance, rather than saying this:

        protocol Matchable<MatchingValue> {
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable<Self> {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable<Bound> {}
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value:
Bound) -> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Matchable<Matching>
                typealias Value = OutValue

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) {
patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

You could instead say:

        protocol Matchable {
                @overloadable associatedtype MatchingValue
                func ~= (pattern: Self, value: MatchingValue) -> Bool
        }

        protocol Equatable: Matchable where MatchingValue |= Self {
                func == (lhs: Self, rhs: Self) -> Bool
        }
        func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
                return lhs == rhs
        }

        extension Range: Equatable, Matchable {
                typealias MatchingValue |= Bound
        }
        func ~= <Bound: Comparable>(pattern: Range<Bound>, value:
Bound) -> Bool {
                return pattern.lowerBound <= value && value <
pattern.upperBound
        }

        struct PatternDictionary<Matching, Value>:
DictionaryLiteralConvertible {
                typealias Key = Any<Matchable where .MatchingValue &
>
                typealias Value = Value

                var patterns: DictionaryLiteral<Key, Value>
                init(dictionaryLiteral pairs: (Key, Value)...) {
patterns = DictionaryLiteral(pairs) }

                subscript(matchingValue: Matching) -> Value? {
                        for (pattern, value) in patterns {
                                if pattern ~= matchingValue {
                                        return value
                                }
                        }
                        return nil
                }
        }

(Is `MatchingValue |= Bound` a union type feature? I'm not sure. It
does have the syntax of one, but there's a separate overload for each type,
so I don't think it really acts like one.)

This is very nearly the same feature, but presented with different
syntax—effectively with a different metaphor. That should prevent it from
being abused the way the core team fears it will be.

(One difference is that this version permits "vacuous" conformances: in
theory, there's no reason you couldn't conform to a protocol with an
`@overloadable associatedtype` and define zero types. On the other hand,
that's not necessarily *wrong*, and might even be useful in some cases.)

--
Brent Royal-Gordon
Architechies

_______________________________________________
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

--
Dan Appel


(Anton Zhilin) #16

Copy of link to the proposal:
https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

Inline:

Awesome that you guys started this! I've been meaning to pitch it for a
while. Couple notes:

- in the motivation, you're conforming to SequenceType while defining a
From protocol (just a typo I think)

Fixed

- the proposal should mention Brent's comments since it doesn't have a
rebuttal to the "unlikely" status generic protocols were given in the
generic manifesto. Brent does a great job of doing that

Added a bold link

- is there a way for generic protocols to still have associated types? I
think that could have some interesting use cases.

Generic protocols can contain associated types, but no type can conform to
multiple instances of such protocols.

Actually, this limitation is more restricting than it seems, because if our
protocol conforms to a protocol with associated type requirements, then we
still can't conform to our protocol multiple times.

···

2016-06-12 21:51 GMT+03:00 Dan Appel <dan.appel00@gmail.com>:

Dan Appel

On Sun, Jun 12, 2016 at 11:28 AM Антон Жилин <swift-evolution@swift.org> > wrote:

Yes, everything that works on generic types should work for generic
protocols. I'll add that.

What won't work is declaring that MyComparable : Comparable iff T ==
Self. The same won't work for current non-generic protocols as well.
Although that feature is highly requested, it is discussed in a separate
proposal.

- Anton

2016-06-12 21:16 GMT+03:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution@swift.org> >>> wrote:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

When you propose this:
Syntax in protocol extensions

protocol MyComparable<T> {
  func < (left: Self, right: T)
}extension MyComparable {
  func > (left: T, right: Self) {
    return right < left
  }
}

Would it be possible for me to write something like:

extension MyComparable<T : SignedNumber> { ... }

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

--
Dan Appel


(Dan Appel) #17

if our protocol conforms to a protocol with associated type requirements,

then we still can't conform to our protocol multiple times.

I don't think so:

protocol A {

    associatedtype TypeA

}

protocol B<TypeB>: A {

    func convert(from: TypeB) -> TypeA

}

struct C: A {

    typealias TypeA = String

}

extension C: B<Int> {

    func convert(from int: Int) -> String {

        return String(int)

    }

}

extension C: B<Double> {

    func convert(from double: Double) -> String {

        return String(double)

    }

}

Seems to me like the above should work fine: the associated type is
declared once, but the generic protocol is conformed to multiple times.

···

On Sun, Jun 12, 2016 at 1:22 PM Антон Жилин <antonyzhilin@gmail.com> wrote:

Copy of link to the proposal:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

Inline:

2016-06-12 21:51 GMT+03:00 Dan Appel <dan.appel00@gmail.com>:

Awesome that you guys started this! I've been meaning to pitch it for a
while. Couple notes:

- in the motivation, you're conforming to SequenceType while defining a
From protocol (just a typo I think)

Fixed

- the proposal should mention Brent's comments since it doesn't have a
rebuttal to the "unlikely" status generic protocols were given in the
generic manifesto. Brent does a great job of doing that

Added a bold link

- is there a way for generic protocols to still have associated types? I
think that could have some interesting use cases.

Generic protocols can contain associated types, but no type can conform to
multiple instances of such protocols.

Actually, this limitation is more restricting than it seems, because if
our protocol conforms to a protocol with associated type requirements, then
we still can't conform to our protocol multiple times.

Dan Appel

On Sun, Jun 12, 2016 at 11:28 AM Антон Жилин <swift-evolution@swift.org> >> wrote:

Yes, everything that works on generic types should work for generic

protocols. I'll add that.

What won't work is declaring that MyComparable : Comparable iff T ==
Self. The same won't work for current non-generic protocols as well.
Although that feature is highly requested, it is discussed in a separate
proposal.

- Anton

2016-06-12 21:16 GMT+03:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution@swift.org >>>> > wrote:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

When you propose this:
Syntax in protocol extensions

protocol MyComparable<T> {
  func < (left: Self, right: T)
}extension MyComparable {
  func > (left: T, right: Self) {
    return right < left
  }
}

Would it be possible for me to write something like:

extension MyComparable<T : SignedNumber> { ... }

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

--
Dan Appel

--

Dan Appel


(Anton Zhilin) #18

Right. If associated type has the same value for both conformances, then
everything should be fine. Fixed that example.

- Anton

···

2016-06-13 0:15 GMT+03:00 Dan Appel <dan.appel00@gmail.com>:

>if our protocol conforms to a protocol with associated type requirements,
then we still can't conform to our protocol multiple times.

I don't think so:

protocol A {

    associatedtype TypeA

}

protocol B<TypeB>: A {

    func convert(from: TypeB) -> TypeA

}

struct C: A {

    typealias TypeA = String

}

extension C: B<Int> {

    func convert(from int: Int) -> String {

        return String(int)

    }

}

extension C: B<Double> {

    func convert(from double: Double) -> String {

        return String(double)

    }

}

Seems to me like the above should work fine: the associated type is
declared once, but the generic protocol is conformed to multiple times.

On Sun, Jun 12, 2016 at 1:22 PM Антон Жилин <antonyzhilin@gmail.com> > wrote:

Copy of link to the proposal:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

Inline:

2016-06-12 21:51 GMT+03:00 Dan Appel <dan.appel00@gmail.com>:

Awesome that you guys started this! I've been meaning to pitch it for a
while. Couple notes:

- in the motivation, you're conforming to SequenceType while defining a
From protocol (just a typo I think)

Fixed

- the proposal should mention Brent's comments since it doesn't have a
rebuttal to the "unlikely" status generic protocols were given in the
generic manifesto. Brent does a great job of doing that

Added a bold link

- is there a way for generic protocols to still have associated types? I
think that could have some interesting use cases.

Generic protocols can contain associated types, but no type can conform
to multiple instances of such protocols.

Actually, this limitation is more restricting than it seems, because if
our protocol conforms to a protocol with associated type requirements, then
we still can't conform to our protocol multiple times.

Dan Appel

On Sun, Jun 12, 2016 at 11:28 AM Антон Жилин <swift-evolution@swift.org> >>> wrote:

Yes, everything that works on generic types should work for generic

protocols. I'll add that.

What won't work is declaring that MyComparable : Comparable iff T ==
Self. The same won't work for current non-generic protocols as well.
Although that feature is highly requested, it is discussed in a
separate proposal.

- Anton

2016-06-12 21:16 GMT+03:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин < >>>>> swift-evolution@swift.org> wrote:

I've prepared a proper draft:

https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md

When you propose this:
Syntax in protocol extensions

protocol MyComparable<T> {
  func < (left: Self, right: T)
}extension MyComparable {
  func > (left: T, right: Self) {
    return right < left
  }
}

Would it be possible for me to write something like:

extension MyComparable<T : SignedNumber> { ... }

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

--
Dan Appel

--

Dan Appel