[Proposal] Separate protocols and interfaces

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

···

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com> wrote:

I think this should be possible as SequenceType.Generator is in a covariant position and changes covariantly with the Element type.
The type should use the same bounds as that of the SequenceType:

var zs: protocol<SequenceType where Generator.Element : SomeProtocol>
var gz: protocol<SequenceType.GeneratorType where .Element : SomeProtocol> = zs.generate()
var z: SomeProtocol? = gz.next()

If no bounds would have been given Element would have the type Any.

Should we have path-dependent types like Scala?
(see e.g. What is meant by Scala's path-dependent types? - Stack Overflow)

var zs: protocol<SequenceType where Generator.Element : SomeProtocol>
var gz: zs.Generator = zs.generate()
var z: SomeProtocol? = gz.next()
var ys: protocol<SequenceType where Generator.Element : SomeProtocol>
var gy: ys.Generator = gz // type error: path-dependent types ys.Generator and zs.Generator differ

-Thorsten

···

Am 18.01.2016 um 21:18 schrieb Douglas Gregor via swift-evolution <swift-evolution@swift.org>:

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Sure, here's the start of the thread: [swift-evolution] union types

Thanks. Joe was basically saying is that associated types would be automatically bound to the existential for their constraints, or Any if there are no constraints.

He didn’t specifically mention anything about Self, but I suppose Self requirements could also be automatically bound to Any if the existential type doesn’t specify anything more specific, although I’m not sure I would like that behavior.

Self is what would apply in the case of:

func compareTwo(first: Comparable, _ second: Comparable) -> Int { // error!
  if first < second {
    return -1
  }
  //...
}

If Self were automatically bound to Any what would this do? Would it compile and invoke a `<` operator that takes two Any parameters? That doesn’t seem to make sense to me. It certainly wouldn’t guarantee you get the correct behavior if first and second were both Int for example.

···

On Jan 3, 2016, at 9:14 PM, Drew Crawford <drew@sealedabstract.com> wrote:

On Jan 3, 2016, at 9:10 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 3, 2016, at 9:08 PM, Drew Crawford <drew@sealedabstract.com <mailto:drew@sealedabstract.com>> wrote:

Existentials for protocols with Self and / or associated type requirements would require bindings for Self and / or the associated type(s). At least when you use a member that contains Self and / or an associated type in its signature. So the previous example will always fail to compile.

Not true. Joe Groff:

Can you point me to the source? I would like more context around these comments.

This seems like it would be addressed just by allowing Factory to be used as a dynamic type, with its Product type generalized to Any. We'll be set up to support that with some runtime work to store associated types in protocol witness tables (which is also necessary to fix cyclic conformances, one of our Swift 3 goals).

Yeah, when generalizing a protocol type, we ought to be able to either generalize the associated types to their upper bounds, for use cases like yours, or constrain them to specific types, for the AnyGenerator<T> kind of case.

I did think of an additional type constraint needed now due to the additional flexibility in the proposed type system.

Today, The != operator might be defined as follows

func !=<T:Equatable>(lhs:T, rhs:T) -> Bool {
    return !(lhs==rhs)
}

Since the only way today for there to be a reference to something equatable is for you to have a concrete implementing type, this will get the appropriate view of Equatable (e.g. can see the ==(_:Self,_:Self)->Bool method)

However, since the type system has expanded to now allow references to partially constrained protocols, the above is insufficient to provide access to the == operator. Example:

let x:Hashable = "Foo"
let y:Hashable = "Bar"

x != y // Not defined, since there is no ==(_:Hashable, _:Hashable)->Bool

We need a way to differentiate when we are trying to statically require a concrete type. One could reuse one of the existing keywords or an operator, but for now I’ll expand my placeholder syntax with a placeholder keyword, “concrete”

func !=<concrete T:Equatable>(lhs:T, rhs:T) -> Bool {

or from the SequenceType version I posted yesterday:

extension SequenceType where concrete Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

(Another option for a new syntax would be to require explicit syntax for using Equatable as a existential type - i’m deferring that until there is more successful discussion on the ideas themselves.)

-DW

I like the idea to use protocol<> for an abstraction with many constrained protocols but for a single type constraint it is a bit verbose.

What about generics in protocols which are only a view to its associated types or generics which create/are associated types?

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration (see reason below)
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let this still compile:

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

Implementation:
The function declaration above could be rewritten to using a function like generic parameter parameter syntax for protocols:

        // Declaration of SequenceType
        protocol SequenceType<Element: Generator.Element, Generator: GeneratorType, SubSequence> { … }
    // ^~~~ using : in oder to allow default types with =

        // Using "_" to leave it unspecified eg. Any
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>, SubSequence: _>){}
    // omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>>){}
    // omitting `Generator: GeneratorType<Int>` since the type of `Generator` can be inferred from Element
    func afunction(s: SequenceType<Element: Int>){}

The order of arguments is in this case irrelevant, but should probably be in a strict ordering to make it more consistent with the point below.

In order to have a more general generic parameter behavior (to work with structs, classes and enums) we could also allow to use these without external names and only their order. So the example above would look like this:

    func afunction(s: SequenceType<Int, GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<Int, GeneratorType<Int>>){}
    func afunction(s: SequenceType<Int>){}
    
    // These two functions don't produce an error since `Element` can be inferred
    func afunction(s: SequenceType<GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<GeneratorType<Int>>){}

    // for an unconstrained Sequence "_" can be used in order to make it clear: "there could be generic parameters"
    func afunction(s: SequenceType<_>){}

For instance `Array` and `Dictionary` also can apply to this model.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types (currently).

What do you think about this approach?

Should SequenceType<_> be equal to SequenceType<Any> / SequenceType<Element: Any> ?

- Maximilian

···

Am 19.01.2016 um 00:32 schrieb David Waite via swift-evolution <swift-evolution@swift.org>:

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

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

Sure, here's the start of the thread: [swift-evolution] union types

Thanks. Joe was basically saying is that associated types would be automatically bound to the existential for their constraints, or Any if there are no constraints.

He didn’t specifically mention anything about Self, but I suppose Self requirements could also be automatically bound to Any if the existential type doesn’t specify anything more specific, although I’m not sure I would like that behavior.

Self is what would apply in the case of:

func compareTwo(first: Comparable, _ second: Comparable) -> Int { // error!
  if first < second {
    return -1
  }
  //...
}

If Self were automatically bound to Any what would this do? Would it compile and invoke a `<` operator that takes two Any parameters? That doesn’t seem to make sense to me. It certainly wouldn’t guarantee you get the correct behavior if first and second were both Int for example.

I gave this some further thought last night and realized what would happen here is pretty clear. I hadn’t considered existentials where associated types aren’t bound to concrete types before so it just took a few minutes to work through.

Existentials reference a witness table pointing to an actual implementations of the protocol requirements. Actual implementations require parameters of concrete types. This means that you must know what that concrete type is and supply a value of that type in order to actually call the member. The implication of this is that members which require parameters of an associated type that is not bound to a concrete type will not be available on that existential.

In this example, `<` requires two arguments of type Self. However, the `Comparable` existential, if allowed, would have Self bound to `Any`, not a concrete type. Therefore `<` would not be available and you would receive a compiler error on that line. It would be different than the current error and on a different line of code but it would still fail to compile.

With return types, you do not necessarily need to know the concrete type returned by the implementation. Swift could theoretically box the result itself into an existential and return that to the caller. I do not know whether this is the actual design that will be implemented or not.

If I any of the above details are incorrect I hope Joe or someone else will correct me. :)

Matthew

···

On Jan 3, 2016, at 10:44 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 3, 2016, at 9:14 PM, Drew Crawford <drew@sealedabstract.com <mailto:drew@sealedabstract.com>> wrote:

On Jan 3, 2016, at 9:10 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 3, 2016, at 9:08 PM, Drew Crawford <drew@sealedabstract.com <mailto:drew@sealedabstract.com>> wrote:

Existentials for protocols with Self and / or associated type requirements would require bindings for Self and / or the associated type(s). At least when you use a member that contains Self and / or an associated type in its signature. So the previous example will always fail to compile.

Not true. Joe Groff:

Can you point me to the source? I would like more context around these comments.

This seems like it would be addressed just by allowing Factory to be used as a dynamic type, with its Product type generalized to Any. We'll be set up to support that with some runtime work to store associated types in protocol witness tables (which is also necessary to fix cyclic conformances, one of our Swift 3 goals).

Yeah, when generalizing a protocol type, we ought to be able to either generalize the associated types to their upper bounds, for use cases like yours, or constrain them to specific types, for the AnyGenerator<T> kind of case.

I like the idea to use protocol<> for an abstraction with many constrained protocols but for a single type constraint it is a bit verbose.

What about generics in protocols which are only a view to its associated types or generics which create/are associated types?

Since I imagine reusing generics syntax for constraining protocols will be a popular proposal, I figure I’ll give the reasoning I personally didn’t use, at it for my initial placeholder syntax.

The main reason l chose an alternate syntax was that generic type usage and protocol constraints aren’t expressed the same way - protocol constraints on associated types aren’t positional and can be missing. This meant that ConcreteThing<…> and ProtocolType<…> would have different syntaxes inside the angle brackets, and thus felt like it would promote more syntax bike shedding (in addition to personally seeming less clean to me).

Using ProtocolType<…> syntax would I believe be the 5th syntactic usage for <...> (generic type declaration, generic type use, generic constraints on methods, protocol union and now generic protocol use). Swift gets away with the multiple usages today because the syntax within the angle brackets is very similar. Expanding protocol unions meant I stayed closer to generic constraint syntax, while still having was only four syntactic usages (as in, protocol unions would umbrella under/into partial protocol constraints as a new existential type redefinition “thing")

As an example, the liberties I took with SequenceType took it down to a single associated type, but CollectionType would still have a minimum of Index and Element. I would need to declare CollectionType<where .Index == Index> or similar if I were wishing to partially constrain just one parameter. This is also eliminates most of the typing savings of using generic type usage syntax.

Finally and to a lesser degree, the parameters for a generic type define a single concrete type, while for a protocol it defines a view of some concrete type. Today I can intuit Foo<String> is the name of a concrete type. During proposal, there was more value in having a placeholder syntax that did not damage intuition about concrete types.

-DW

···

On Jan 19, 2016, at 7:49 AM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration (see reason below)
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let this still compile:

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

Implementation:
The function declaration above could be rewritten to using a function like generic parameter parameter syntax for protocols:

        // Declaration of SequenceType
        protocol SequenceType<Element: Generator.Element, Generator: GeneratorType, SubSequence> { … }
    // ^~~~ using : in oder to allow default types with =

        // Using "_" to leave it unspecified eg. Any
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>, SubSequence: _>){}
    // omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>>){}
    // omitting `Generator: GeneratorType<Int>` since the type of `Generator` can be inferred from Element
    func afunction(s: SequenceType<Element: Int>){}

The order of arguments is in this case irrelevant, but should probably be in a strict ordering to make it more consistent with the point below.

In order to have a more general generic parameter behavior (to work with structs, classes and enums) we could also allow to use these without external names and only their order. So the example above would look like this:

    func afunction(s: SequenceType<Int, GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<Int, GeneratorType<Int>>){}
    func afunction(s: SequenceType<Int>){}
    
    // These two functions don't produce an error since `Element` can be inferred
    func afunction(s: SequenceType<GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<GeneratorType<Int>>){}

    // for an unconstrained Sequence "_" can be used in order to make it clear: "there could be generic parameters"
    func afunction(s: SequenceType<_>){}

For instance `Array` and `Dictionary` also can apply to this model.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types (currently).

What do you think about this approach?

Should SequenceType<_> be equal to SequenceType<Any> / SequenceType<Element: Any> ?

- Maximilian

Am 19.01.2016 um 00:32 schrieb David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

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

I like the idea to use protocol<> for an abstraction with many constrained protocols but for a single type constraint it is a bit verbose.

What about generics in protocols which are only a view to its associated types or generics which create/are associated types?

Since I imagine reusing generics syntax for constraining protocols will be a popular proposal, I figure I’ll give the reasoning I personally didn’t use, at it for my initial placeholder syntax.

The main reason l chose an alternate syntax was that generic type usage and protocol constraints aren’t expressed the same way - protocol constraints on associated types aren’t positional and can be missing. This meant that ConcreteThing<…> and ProtocolType<…> would have different syntaxes inside the angle brackets, and thus felt like it would promote more syntax bike shedding (in addition to personally seeming less clean to me).

Using ProtocolType<…> syntax would I believe be the 5th syntactic usage for <...> (generic type declaration, generic type use, generic constraints on methods, protocol union and now generic protocol use). Swift gets away with the multiple usages today because the syntax within the angle brackets is very similar. Expanding protocol unions meant I stayed closer to generic constraint syntax, while still having was only four syntactic usages (as in, protocol unions would umbrella under/into partial protocol constraints as a new existential type redefinition “thing")

As an example, the liberties I took with SequenceType took it down to a single associated type, but CollectionType would still have a minimum of Index and Element. I would need to declare CollectionType<where .Index == Index> or similar if I were wishing to partially constrain just one parameter. This is also eliminates most of the typing savings of using generic type usage syntax.

Right. There’s also SubSequence, which you almost certainly don’t want to have to specify. For reference, once we get generic typealiases, one would be free to add

  typealias AnyCollection<Element> = protocol<CollectionType where .Element == Element>

to cover the common case.

  - Doug

···

On Jan 19, 2016, at 10:47 AM, David Waite <david@alkaline-solutions.com> wrote:

On Jan 19, 2016, at 7:49 AM, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration (see reason below)
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let this still compile:

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

Implementation:
The function declaration above could be rewritten to using a function like generic parameter parameter syntax for protocols:

        // Declaration of SequenceType
        protocol SequenceType<Element: Generator.Element, Generator: GeneratorType, SubSequence> { … }
    // ^~~~ using : in oder to allow default types with =

        // Using "_" to leave it unspecified eg. Any
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>, SubSequence: _>){}
    // omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>>){}
    // omitting `Generator: GeneratorType<Int>` since the type of `Generator` can be inferred from Element
    func afunction(s: SequenceType<Element: Int>){}

The order of arguments is in this case irrelevant, but should probably be in a strict ordering to make it more consistent with the point below.

In order to have a more general generic parameter behavior (to work with structs, classes and enums) we could also allow to use these without external names and only their order. So the example above would look like this:

    func afunction(s: SequenceType<Int, GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<Int, GeneratorType<Int>>){}
    func afunction(s: SequenceType<Int>){}
    
    // These two functions don't produce an error since `Element` can be inferred
    func afunction(s: SequenceType<GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<GeneratorType<Int>>){}

    // for an unconstrained Sequence "_" can be used in order to make it clear: "there could be generic parameters"
    func afunction(s: SequenceType<_>){}

For instance `Array` and `Dictionary` also can apply to this model.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types (currently).

What do you think about this approach?

Should SequenceType<_> be equal to SequenceType<Any> / SequenceType<Element: Any> ?

- Maximilian

Am 19.01.2016 um 00:32 schrieb David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

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

Right. There’s also SubSequence, which you almost certainly don’t want to have to specify. For reference, once we get generic typealiases, one would be free to add

Yes, although in the version of SequenceType which declared earlier in the thread, there was no longer a need for SubSequence, as I could just return protocol<SequenceType where .Element == Element> where needed.

  typealias AnyCollection<Element> = protocol<CollectionType where .Element == Element>

Eventually if you had typealiases in protocols (since the keyword will be freed up eventually due to the new associatedtype keyword) you could declare

protocol SequenceType {
  associatedtype Element
  typealias SubSequence = protocol<SequenceType where .Element = Self.Element>

But thats a whole bucket of deprecation issues which aren’t worth it ATM - it would be a proposal for Swift 4.

-DW

···

On Jan 19, 2016, at 1:21 PM, Douglas Gregor <dgregor@apple.com> wrote:

to cover the common case.

  - Doug

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration (see reason below)
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let this still compile:

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

Implementation:
The function declaration above could be rewritten to using a function like generic parameter parameter syntax for protocols:

        // Declaration of SequenceType
        protocol SequenceType<Element: Generator.Element, Generator: GeneratorType, SubSequence> { … }
    // ^~~~ using : in oder to allow default types with =

        // Using "_" to leave it unspecified eg. Any
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>, SubSequence: _>){}
    // omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>>){}
    // omitting `Generator: GeneratorType<Int>` since the type of `Generator` can be inferred from Element
    func afunction(s: SequenceType<Element: Int>){}

The order of arguments is in this case irrelevant, but should probably be in a strict ordering to make it more consistent with the point below.

In order to have a more general generic parameter behavior (to work with structs, classes and enums) we could also allow to use these without external names and only their order. So the example above would look like this:

    func afunction(s: SequenceType<Int, GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<Int, GeneratorType<Int>>){}
    func afunction(s: SequenceType<Int>){}
    
    // These two functions don't produce an error since `Element` can be inferred
    func afunction(s: SequenceType<GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<GeneratorType<Int>>){}

    // for an unconstrained Sequence "_" can be used in order to make it clear: "there could be generic parameters"
    func afunction(s: SequenceType<_>){}

For instance `Array` and `Dictionary` also can apply to this model.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types (currently).

What do you think about this approach?

Should SequenceType<_> be equal to SequenceType<Any> / SequenceType<Element: Any> ?

- Maximilian

Am 19.01.2016 um 00:32 schrieb David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

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

Right. There’s also SubSequence, which you almost certainly don’t want to have to specify. For reference, once we get generic typealiases, one would be free to add

Yes, although in the version of SequenceType which declared earlier in the thread, there was no longer a need for SubSequence, as I could just return protocol<SequenceType where .Element == Element> where needed.

I suspect that there is a class of algorithms for which it is interesting to limit ourselves to “SubSequence == Self”, which one would lose by making SubSequence an existential.

  - Doug

···

On Jan 19, 2016, at 12:54 PM, David Waite <david@alkaline-solutions.com> wrote:

On Jan 19, 2016, at 1:21 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

  typealias AnyCollection<Element> = protocol<CollectionType where .Element == Element>

Eventually if you had typealiases in protocols (since the keyword will be freed up eventually due to the new associatedtype keyword) you could declare

protocol SequenceType {
  associatedtype Element
  typealias SubSequence = protocol<SequenceType where .Element = Self.Element>

But thats a whole bucket of deprecation issues which aren’t worth it ATM - it would be a proposal for Swift 4.

-DW

to cover the common case.

  - Doug

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration (see reason below)
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let this still compile:

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

Implementation:
The function declaration above could be rewritten to using a function like generic parameter parameter syntax for protocols:

        // Declaration of SequenceType
        protocol SequenceType<Element: Generator.Element, Generator: GeneratorType, SubSequence> { … }
    // ^~~~ using : in oder to allow default types with =

        // Using "_" to leave it unspecified eg. Any
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>, SubSequence: _>){}
    // omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>>){}
    // omitting `Generator: GeneratorType<Int>` since the type of `Generator` can be inferred from Element
    func afunction(s: SequenceType<Element: Int>){}

The order of arguments is in this case irrelevant, but should probably be in a strict ordering to make it more consistent with the point below.

In order to have a more general generic parameter behavior (to work with structs, classes and enums) we could also allow to use these without external names and only their order. So the example above would look like this:

    func afunction(s: SequenceType<Int, GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<Int, GeneratorType<Int>>){}
    func afunction(s: SequenceType<Int>){}
    
    // These two functions don't produce an error since `Element` can be inferred
    func afunction(s: SequenceType<GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<GeneratorType<Int>>){}

    // for an unconstrained Sequence "_" can be used in order to make it clear: "there could be generic parameters"
    func afunction(s: SequenceType<_>){}

For instance `Array` and `Dictionary` also can apply to this model.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types (currently).

What do you think about this approach?

Should SequenceType<_> be equal to SequenceType<Any> / SequenceType<Element: Any> ?

- Maximilian

Am 19.01.2016 um 00:32 schrieb David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

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

So something like the following?

  protocol SequenceType {
      associatedtype Element
      associatedtype SubSequence:protocol<SequenceType where .Element == Element>
      …
  }
  
  extension SequenceType where SubSequence == Self {
      …
  }

-DW

···

On Jan 19, 2016, at 3:45 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 19, 2016, at 12:54 PM, David Waite <david@alkaline-solutions.com> wrote:

On Jan 19, 2016, at 1:21 PM, Douglas Gregor <dgregor@apple.com> wrote:

Right. There’s also SubSequence, which you almost certainly don’t want to have to specify. For reference, once we get generic typealiases, one would be free to add

Yes, although in the version of SequenceType which declared earlier in the thread, there was no longer a need for SubSequence, as I could just return protocol<SequenceType where .Element == Element> where needed.

I suspect that there is a class of algorithms for which it is interesting to limit ourselves to “SubSequence == Self”, which one would lose by making SubSequence an existential.

  - Doug

See inline

I like the idea to use protocol<> for an abstraction with many constrained protocols but for a single type constraint it is a bit verbose.

What about generics in protocols which are only a view to its associated types or generics which create/are associated types?

Since I imagine reusing generics syntax for constraining protocols will be a popular proposal, I figure I’ll give the reasoning I personally didn’t use, at it for my initial placeholder syntax.

The main reason l chose an alternate syntax was that generic type usage and protocol constraints aren’t expressed the same way - protocol constraints on associated types aren’t positional and can be missing. This meant that ConcreteThing<…> and ProtocolType<…> would have different syntaxes inside the angle brackets, and thus felt like it would promote more syntax bike shedding (in addition to personally seeming less clean to me).

Using ProtocolType<…> syntax would I believe be the 5th syntactic usage for <...> (generic type declaration, generic type use, generic constraints on methods, protocol union and now generic protocol use). Swift gets away with the multiple usages today because the syntax within the angle brackets is very similar. Expanding protocol unions meant I stayed closer to generic constraint syntax, while still having was only four syntactic usages (as in, protocol unions would umbrella under/into partial protocol constraints as a new existential type redefinition “thing")

I think we could make ConcreteThing<…> and ProtocolType<…> equal in terms of generic syntax (which could be "redefined" as I proposed). Where you can also omit generic parameters for example in dictionary:

Dictionary<Key: _ , Value: String>
// or short form
Dictionary< _ , String>
// even shorter
[ _ : String]

Where the Key of Dictionary is of any type which conforms to `Hashable`.

Note: `Hashable` is self constrained and behaviors of an any type of a self constrained protocol has to be discussed.
Second note: This is probably a bad example in terms of usability but it should only show the similar generic syntax (see my proposal).

As an example, the liberties I took with SequenceType took it down to a single associated type, but CollectionType would still have a minimum of Index and Element. I would need to declare CollectionType<where .Index == Index> or similar if I were wishing to partially constrain just one parameter. This is also eliminates most of the typing savings of using generic type usage syntax.

I thought the proposed syntax would be:

// for a concrete index of type Int
protocol<CollectionType where .Index == Int>

What do you think about my proposed syntax ?

CollectionType<Index: Int>

Finally and to a lesser degree, the parameters for a generic type define a single concrete type, while for a protocol it defines a view of some concrete type. Today I can intuit Foo<String> is the name of a concrete type. During proposal, there was more value in having a placeholder syntax that did not damage intuition about concrete types.

What do you mean by "intuition about concrete types" ?
Why not allowing Foo< _ > (to be for instance a covariant type to all Foo) ? It's similar to a protocol, but it is a more concrete type where you don't have to define a protocol to use Foo as a standalone type in a more general way.

- Maximilian

···

Am 19.01.2016 um 19:47 schrieb David Waite <david@alkaline-solutions.com>:

On Jan 19, 2016, at 7:49 AM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

-DW

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration (see reason below)
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let this still compile:

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

Implementation:
The function declaration above could be rewritten to using a function like generic parameter parameter syntax for protocols:

        // Declaration of SequenceType
        protocol SequenceType<Element: Generator.Element, Generator: GeneratorType, SubSequence> { … }
    // ^~~~ using : in oder to allow default types with =

        // Using "_" to leave it unspecified eg. Any
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>, SubSequence: _>){}
    // omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Element: Int, Generator: GeneratorType<Element: Int>>){}
    // omitting `Generator: GeneratorType<Int>` since the type of `Generator` can be inferred from Element
    func afunction(s: SequenceType<Element: Int>){}

The order of arguments is in this case irrelevant, but should probably be in a strict ordering to make it more consistent with the point below.

In order to have a more general generic parameter behavior (to work with structs, classes and enums) we could also allow to use these without external names and only their order. So the example above would look like this:

    func afunction(s: SequenceType<Int, GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<Int, GeneratorType<Int>>){}
    func afunction(s: SequenceType<Int>){}
    
    // These two functions don't produce an error since `Element` can be inferred
    func afunction(s: SequenceType<GeneratorType<Int>, _>){}
    func afunction(s: SequenceType<GeneratorType<Int>>){}

    // for an unconstrained Sequence "_" can be used in order to make it clear: "there could be generic parameters"
    func afunction(s: SequenceType<_>){}

For instance `Array` and `Dictionary` also can apply to this model.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types (currently).

What do you think about this approach?

Should SequenceType<_> be equal to SequenceType<Any> / SequenceType<Element: Any> ?

- Maximilian

Am 19.01.2016 um 00:32 schrieb David Waite via swift-evolution <swift-evolution@swift.org>:

On Jan 18, 2016, at 1:18 PM, Douglas Gregor <dgregor@apple.com> wrote:

let x:SequenceType = [1,2,3] // no constraints specified whatsoever
let y:protocol<SequenceType where Generator.Element == String> = [“foo”, “bar”] // partially constrained

Not wanting to start throwing paint, but "Generator.Element" could be ambiguous with a Generator in the lexical scope. You'll probably want some way to differentiate it (eg, a leading dot). Otherwise, this is the syntactic direction that I think makes the most sense.

Yes, I’ll use that below. There is a right balance in extending protocol<>. For instance, I did not like the following at all:

  protocol<S:SequenceType where S.Generator.Element == String>

One interesting side-effect to note is that SequenceType could be redefined to only have “Element” as an associated type. Instead of Generator or SubSequence being associated types, methods could return partially constrained GenericType or SequenceType from the appropriate methods. This would be one way of eliminating issues today with recursive use of associated types (as well as SubSequence over-constraining the output of various algorithms to a single concrete type)

Assuming nobody cares about the identity of the Generator type, which is probably a safe assumption. Note that one could implement this design today by using a function taking no arguments and returning an Element? in lieu of "Generator”.

Yes, as an aside I was actually curious GeneratorType existed when I was first diving into the standard library, considering it could just be a closure.

For the “opening” of an existential type to find the concrete type it stores dynamically, I’m currently using a different syntax just because the “open x as T” originally given makes ‘open’ a keyword’ and makes it unclear where ’T’ came from

Yes, the issue of "which name did I introduce?" is tricky here.

- I’m instead overloading the typealias keyword when used within an expression context:

typealias T = x.dynamicType
let xT = x as! T

x will have to be immutable for this to make sense. That's why my ugly "open" expression extracts a new value and gives it a fresh type in one lexically-scoped block. Both the type and the value end up being scoped. Otherwise, one might reassign "x" with a different dynamic type... Then what does T mean?

This is an area that I need to understand compiler behavior more here (I’ve been researching)

If ’T’ internally behaves like an immutable variable with lexical scope, then ’typealias’ in a code block is just another statement, and the type would be based on the value of ‘x’ at the point of execution:

var x:Any = “Initial”
typealias T = x.dynamicType // String
x = 1
let xT = x as! T // fails precondition as if I had said x as! String

I’m guessing from your comment however that T would not a variable. In which case, it makes sense to be more restrictive in use (such as requiring immutability). This is also more consistent with the use of typealias in other contexts, if that was the syntax one was going with.

I suggest you also look at what can be done with an existential value that hasn't been opened explicitly. Can I call "generate" on a SequenceType value, and what do I get back?

Depends on how far we are willing to go. If you are only willing to expose the invariants in the face of the constraints given (in this case none), all you would get exposed is “underestimateCount”.

If you are willing to expose anything which can be expressed by certain type safety rules (needing formal definition later), then you can do quite a bit.

To start, let me express SequenceType relying on the partially constrained protocols, taking a few liberties:
- pruning some alternate forms
- removing SubSequence and Generator associated types and just having Element
- using the protocol<> syntax described before (with . prefix)
- returning partially constrained SequenceTypes rather than stdlib concrete types like JoinSequence in a few cases.
- eliminate usage of AnySequence in definitions (not needed)
- assuming a change in protocol LazySequenceType to be expressed in terms of Element rather than a base SequenceType

protocol SequenceType {
    associatedtype Element

    var lazy:protocol<LazySequenceType where .Element == Element> { get }
    func contains(@noescape predicate:(Element) throws -> Bool) rethrows -> Bool
    func dropFirst(n:Int) -> protocol<SequenceType where .Element == Element>
    func dropLast(n:Int) -> protocol<SequenceType where .Element == Element>
    func elementsEqual(other:protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element)) -> Bool
    func enumerate() -> protocol<SequenceType where .Element == (Int, Element)>
    func filter(@noescape includeElement: (Element) throws-> Bool) rethrows -> [Element]
    func flatMap<E>(transform: (Element) throws -> protocol<SequenceType where .Element:E>) rethrows -> [E]
    func flatMap<T>(@noescape transform: (Element) throws -> T?) rethrows -> [T]
    func forEach(@noescape body: (Element) throws -> ()) rethrows
    func generate() -> protocol<GeneratorType where .Element == Element>
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>, @noescape isOrderedBefore:(Element,Element) throws-> Bool) rethrows -> Bool
    func map<T>(@noescape transform: (Element) throws -> T) rethrows -> [T]
    func maxElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func minElement(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> Element?
    func prefix(n:Int) -> protocol<SequenceType where .Element == Element>
    func reduce<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T
    func reverse() -> [Element]
    func sort(@noescape isOrderedBefore: (Element,Element) throws -> Bool) rethrows -> [Element]
    func split(maxSplit: Int, allowEmptySubsequences: Bool, @noescape isSeparator: (Element) throws -> Bool) rethrows -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element>, @noescape isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool
    func suffix(n:Int) -> protocol<SequenceType where .Element == Element>
    func underestimateCount() -> Int
}

extension SequenceType where Element:Equatable {
    func elementsEqual<OtherSequence : protocol<SequenceType where .Element == Element>(other: OtherSequence) -> Bool
    func split(separator: Element, maxSplit: Int, allowEmptySlices: Bool) -> [protocol<SequenceType where .Element == Element>]
    func startsWith(other: protocol<SequenceType where .Element == Element) -> Bool
}

extension SequenceType where Element == String {
    func joinWithSeparator(separator:String) -> String
}

extension SequenceType where Element:protocol<SequenceType>
func flatten() -> protocol<SequenceType where .Element == Element.Element>
func joinWithSeparator(separator: protocol<SequenceType where .Element = Element.Element>) -> protocol<SequenceType where .Element == Element.Element>)
}

extension SequenceType where Element:Comparable {
    func lexicographicalCompare(other: protocol<SequenceType where .Element == Element>) -> Bool
    func maxElement() -> Element?
    func minElement() -> Element?
    func sort() -> [Element]
}
Now, mapping this in terms of just an unconstrained SequenceType, which is shorthand for protocol<SequenceType where .Element:Any>:
- None of the extensions match the constraint on Element, so they are not exposed
- property Lazy is exposed as an unconstrained LazySequenceType, aka protocol<LazySequenceType where .Element:Any>
- the predicate function in contains is not constrained by a particular kind of element, so it becomes (Any)->Bool, e.g.:
func contains(@noescape predicate:(Any) throws -> Bool) rethrows -> Bool
- dropFirst/dropLast would return an unconstrained SequenceType. This would need to leverage rules around constrained protocol variance/equivalence.
- elementsEqual was changed to no longer be generic - one can pass in any SequenceType constrained to have the same Element type. The function parameter, similar to contains above, now has the signature (Any, Any)->Bool
- enumerate() will return a sequence with elements of type (Int, Any)
- filter takes a (Any) throws -> Bool method and returns [Any]
- flatMap and forEach are exposed similar to contains above
- generate returns an unconstrained GeneratorType (so it will have a single method func next() -> Any?)
- lexicographicalCompare fails, more below
- reduce has effective syntax:
func reduce<T>(initial: T, @noescape combine: (T, Any) throws -> T) rethrows -> T
- the rest are exposed as above except for startsWith

So all functionality except lexicographicalCompare and startsWith could at least in theory be exposed safely. These cannot be exposed cleanly because method expected an input sequence with the same element type, but the element type is not invariantly constrained. Self properties and method arguments are an equivalent problem, except that for a Self argument can only be invariantly constrained when you know the implementing concrete type - hence wanting a system to dynamically reason about said types.

This seems like a good amount of exposed functionality. Granted, it is not free to work at the level of protocols vs concrete types, and this does not attempt to simplify that. However, this proposal seems to bridge the gap between protocols without associated types and protocols with them. I also am happy with the amount of functionality that can be exposed safely as well as the consistency (A lexicographicalCompare function working on heterogenous sequences would have to be declared differently)

-DW

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

Sure, here's the start of the thread: [swift-evolution] union types

Thanks. Joe was basically saying is that associated types would be automatically bound to the existential for their constraints, or Any if there are no constraints.

He didn’t specifically mention anything about Self, but I suppose Self requirements could also be automatically bound to Any if the existential type doesn’t specify anything more specific, although I’m not sure I would like that behavior.

Self is what would apply in the case of:

func compareTwo(first: Comparable, _ second: Comparable) -> Int { // error!
  if first < second {
    return -1
  }
  //...
}

If Self were automatically bound to Any what would this do? Would it compile and invoke a `<` operator that takes two Any parameters? That doesn’t seem to make sense to me. It certainly wouldn’t guarantee you get the correct behavior if first and second were both Int for example.

I gave this some further thought last night and realized what would happen here is pretty clear. I hadn’t considered existentials where associated types aren’t bound to concrete types before so it just took a few minutes to work through.

Existentials reference a witness table pointing to an actual implementations of the protocol requirements. Actual implementations require parameters of concrete types. This means that you must know what that concrete type is and supply a value of that type in order to actually call the member. The implication of this is that members which require parameters of an associated type that is not bound to a concrete type will not be available on that existential.

There is a concrete type, which is known dynamically to the existential value, but you would need a way to name that type to (e.g.) cast down to it before you could use the member. That’s why the open..as operation I mentioned allows one to use these members: it gives a way to name the type. It actually helps to think of any operation on existentials as implicitly using open..as, because it makes the semantics far more explicit. (The compiler does this internally as well)

In this example, `<` requires two arguments of type Self. However, the `Comparable` existential, if allowed, would have Self bound to `Any`, not a concrete type. Therefore `<` would not be available and you would receive a compiler error on that line. It would be different than the current error and on a different line of code but it would still fail to compile.

With return types, you do not necessarily need to know the concrete type returned by the implementation. Swift could theoretically box the result itself into an existential and return that to the caller. I do not know whether this is the actual design that will be implemented or not.

It’s a reasonable direction that we’ve discussed in passing but never committed to.

  - Doug

···

On Jan 4, 2016, at 6:24 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 3, 2016, at 10:44 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 3, 2016, at 9:14 PM, Drew Crawford <drew@sealedabstract.com <mailto:drew@sealedabstract.com>> wrote:

Yes, exactly. I found 6 occurrences of this in the Swift standard library.

  - Doug

···

On Jan 19, 2016, at 5:23 PM, David Waite <david@alkaline-solutions.com> wrote:

On Jan 19, 2016, at 3:45 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 19, 2016, at 12:54 PM, David Waite <david@alkaline-solutions.com> wrote:

On Jan 19, 2016, at 1:21 PM, Douglas Gregor <dgregor@apple.com> wrote:

Right. There’s also SubSequence, which you almost certainly don’t want to have to specify. For reference, once we get generic typealiases, one would be free to add

Yes, although in the version of SequenceType which declared earlier in the thread, there was no longer a need for SubSequence, as I could just return protocol<SequenceType where .Element == Element> where needed.

I suspect that there is a class of algorithms for which it is interesting to limit ourselves to “SubSequence == Self”, which one would lose by making SubSequence an existential.

  - Doug

So something like the following?

  protocol SequenceType {
      associatedtype Element
      associatedtype SubSequence:protocol<SequenceType where .Element == Element>
      …
  }
  
  extension SequenceType where SubSequence == Self {
      …
  }

I think we could make ConcreteThing<…> and ProtocolType<…> equal in terms of generic syntax (which could be "redefined" as I proposed). Where you can also omit generic parameters for example in dictionary:

Dictionary<Key: _ , Value: String>
// or short form
Dictionary< _ , String>
// even shorter
[ _ : String]

Where the Key of Dictionary is of any type which conforms to `Hashable`.

Note: `Hashable` is self constrained and behaviors of an any type of a self constrained protocol has to be discussed.
Second note: This is probably a bad example in terms of usability but it should only show the similar generic syntax (see my proposal).

I mentally treat self as an associated type bound to the first concrete subtype in the type chain. (first, because of class inheritance).

Re: syntax, like I said I’m trying to defer too much syntax discussion until there is more participation/interest in the concept and approach (I think only four people have participated in this thread over nearly two weeks).

But I’ll bite :-)

It appears you are proposing that generics be expanded to be keyed either by generic type parameter name or positionally and adding _ to positional syntax, while I imagine protocols would remain just keyed by name. Protocols would not have a simplistic order to implement (for example, I could be extending two parent protocols, both with their own associated types)

// for a concrete index of type Int
protocol<CollectionType where .Index == Int>

What do you think about my proposed syntax ?

CollectionType<Index: Int>

I personally think there is a bit too much overlap between ‘where’ syntax, and possibly conflicting usage. In your example, would Index: Foo indicate that index is exactly Foo, is Foo or some subtype, or is a concrete implementation of Foo? If there were two syntaxes, I’d hope one to be a much closer shortening of the other.

Finally and to a lesser degree, the parameters for a generic type define a single concrete type, while for a protocol it defines a view of some concrete type. Today I can intuit Foo<String> is the name of a concrete type. During proposal, there was more value in having a placeholder syntax that did not damage intuition about concrete types.

What do you mean by "intuition about concrete types” ?

Today, you cannot use generics except to declare or talk about a specific concrete type with a specific implementation and behavior - you know you are dealing with a concrete, initializable Foo<Int>. Among other things, you know that the full published API for Foo<Int> is available.

This proposal (excluding type-opening) is about being able to use a protocol with associated types more loosely. My approach is to expose the methods which can be expressed safely and uniformly based on the type constraints available, and to hide the rest. Today, you would have to do this by declaring a new protocol and using extensions to opt concrete types into it, by using generic constraints exclusively, and/or using wrapper types like AnySequence to do type erasure.

Why not allowing Foo< _ > (to be for instance a covariant type to all Foo) ? It's similar to a protocol, but it is a more concrete type where you don't have to define a protocol to use Foo as a standalone type in a more general way.

I do have a mild personal conflict around whether exposing the ability to have partially constrained generics is a good idea, but I admit my hesitation is based on philosophies of good application and API design, and not based on language design.

implicit protocols on types seem pragmatic, because otherwise I’d have to create a protocol just for others to use me.

Allowing a protocol with associated types to be partially constrained seems pragmatic to me, since otherwise I have to use one of the workarounds above (hand-coded new protocol, generic constraints ever, and/or type-erasure struct-wrapper).

Expanding the usage of the implicit protocol on some generic type seems anti-pragmatic, just because it allows someone to go longer before they really evaluate whether locking consumers of their API into a single implementation is a good idea. They can always declare a protocol explicitly if they want partially constrained behavior.

-DW

···

On Jan 21, 2016, at 1:29 PM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

From my perspective, Foo has an implicitly declared protocol. It is a protocol tied to a single implementation (or to subtypes which inherit and override that implementation, in the case of classes).

I think we could make ConcreteThing<…> and ProtocolType<…> equal in terms of generic syntax (which could be "redefined" as I proposed). Where you can also omit generic parameters for example in dictionary:

Dictionary<Key: _ , Value: String>
// or short form
Dictionary< _ , String>
// even shorter
[ _ : String]

Where the Key of Dictionary is of any type which conforms to `Hashable`.

Note: `Hashable` is self constrained and behaviors of an any type of a self constrained protocol has to be discussed.
Second note: This is probably a bad example in terms of usability but it should only show the similar generic syntax (see my proposal).

I mentally treat self as an associated type bound to the first concrete subtype in the type chain. (first, because of class inheritance).

Since Hashable is only self constrained due to Equatable should we even allow this code?

let a: Equatable = 1
let b: Equatable = "2"

a == b

// in this case there could be a more general == function
func == <T: Equatable, U: Equatable>(x: T, y: U) -> Bool {
    if let yAsT = y as? T {
        return yAsT == x }
    return false
}

But it seems like that there has to be a different implementation of `self` constrained functions (like ==) if a self constrained protocol is used without specifying `self`.

Re: syntax, like I said I’m trying to defer too much syntax discussion until there is more participation/interest in the concept and approach (I think only four people have participated in this thread over nearly two weeks).

But I’ll bite :-)

It appears you are proposing that generics be expanded to be keyed either by generic type parameter name or positionally and adding _ to positional syntax, while I imagine protocols would remain just keyed by name.

What do you mean by:

Protocols would not have a simplistic order to implement (for example, I could be extending two parent protocols, both with their own associated types)

Do you mean conforming to a protocol by "extending"?
In this case it would be even more explicit about types:

struct Numbers: SequenceType<Element: Int, SubSequence: Numbers> { ... }

To be clear, I'm proposing:
- Generic syntax for all types including protocols to be the same
- Named generic parameters eg: Array<Element: Int> , CollectionType<Element: String, Index: Int>
- Without name: Array<Int> , CollectionType<String, Int>
- Introduce "_" as placeholder for a covariant "any type" to make protocols and other generic types partially constrained:
Array<Element: _ > , CollectionType<Element: String, Index: _ >
which is the same as
Array< _ > , CollectionType<String, _ > == CollectionType<String> == CollectionType<Element: String>

// for a concrete index of type Int
protocol<CollectionType where .Index == Int>

What do you think about my proposed syntax ?

CollectionType<Index: Int>

I personally think there is a bit too much overlap between ‘where’ syntax, and possibly conflicting usage. In your example, would Index: Foo indicate that index is exactly Foo, is Foo or some subtype, or is a concrete implementation of Foo? If there were two syntaxes, I’d hope one to be a much closer shortening of the other.

Index: Foo
Index is of type Foo or a Subtype.

- If Foo is a struct or a enum, Index can be of type: Foo or a subtype e.g. Foo<Int> and Foo< _ >

- If Foo is a protocol or a class, Index can be of type (additionally to the ones above): subtypes e.g. Subclass<Int> , Subclass< _ > and Int if Int is of type Foo

Finally and to a lesser degree, the parameters for a generic type define a single concrete type, while for a protocol it defines a view of some concrete type. Today I can intuit Foo<String> is the name of a concrete type. During proposal, there was more value in having a placeholder syntax that did not damage intuition about concrete types.

What do you mean by "intuition about concrete types” ?

Today, you cannot use generics except to declare or talk about a specific concrete type with a specific implementation and behavior - you know you are dealing with a concrete, initializable Foo<Int>. Among other things, you know that the full published API for Foo<Int> is available.

From my perspective, Foo has an implicitly declared protocol. It is a protocol tied to a single implementation (or to subtypes which inherit and override that implementation, in the case of classes).

This proposal (excluding type-opening) is about being able to use a protocol with associated types more loosely. My approach is to expose the methods which can be expressed safely and uniformly based on the type constraints available, and to hide the rest. Today, you would have to do this by declaring a new protocol and using extensions to opt concrete types into it, by using generic constraints exclusively, and/or using wrapper types like AnySequence to do type erasure.

Why not allowing Foo< _ > (to be for instance a covariant type to all Foo) ? It's similar to a protocol, but it is a more concrete type where you don't have to define a protocol to use Foo as a standalone type in a more general way.

I do have a mild personal conflict around whether exposing the ability to have partially constrained generics is a good idea, but I admit my hesitation is based on philosophies of good application and API design, and not based on language design.

implicit protocols on types seem pragmatic, because otherwise I’d have to create a protocol just for others to use me.

Allowing a protocol with associated types to be partially constrained seems pragmatic to me, since otherwise I have to use one of the workarounds above (hand-coded new protocol, generic constraints ever, and/or type-erasure struct-wrapper).

Expanding the usage of the implicit protocol on some generic type seems anti-pragmatic, just because it allows someone to go longer before they really evaluate whether locking consumers of their API into a single implementation is a good idea. They can always declare a protocol explicitly if they want partially constrained behavior.

That was also my concern since I want to keep the generic syntax consistent with protocols. So maybe we could make it the same in case of generic parameter names but without the "_" so there are no protocol like behaviors for generic types.

Even though it would be nice to have a covariant Array< _ > type.
Talking about covariance how about (protocol Test {} , Int conforms to Test) a covariant Array<Element: Test> to Array<Int> and an invariant Array<Test>?

- Maximilian

···

Am 22.01.2016 um 03:05 schrieb David Waite <david@alkaline-solutions.com>:

On Jan 21, 2016, at 1:29 PM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

-DW

Sure, here's the start of the thread: [swift-evolution] union types

Thanks. Joe was basically saying is that associated types would be automatically bound to the existential for their constraints, or Any if there are no constraints.

He didn’t specifically mention anything about Self, but I suppose Self requirements could also be automatically bound to Any if the existential type doesn’t specify anything more specific, although I’m not sure I would like that behavior.

Self is what would apply in the case of:

func compareTwo(first: Comparable, _ second: Comparable) -> Int { // error!
  if first < second {
    return -1
  }
  //...
}

If Self were automatically bound to Any what would this do? Would it compile and invoke a `<` operator that takes two Any parameters? That doesn’t seem to make sense to me. It certainly wouldn’t guarantee you get the correct behavior if first and second were both Int for example.

I gave this some further thought last night and realized what would happen here is pretty clear. I hadn’t considered existentials where associated types aren’t bound to concrete types before so it just took a few minutes to work through.

Existentials reference a witness table pointing to an actual implementations of the protocol requirements. Actual implementations require parameters of concrete types. This means that you must know what that concrete type is and supply a value of that type in order to actually call the member. The implication of this is that members which require parameters of an associated type that is not bound to a concrete type will not be available on that existential.

There is a concrete type, which is known dynamically to the existential value, but you would need a way to name that type to (e.g.) cast down to it before you could use the member. That’s why the open..as operation I mentioned allows one to use these members: it gives a way to name the type. It actually helps to think of any operation on existentials as implicitly using open..as, because it makes the semantics far more explicit. (The compiler does this internally as well)

Casting down makes sense and of course you could use the member after that. But why do we need a special cast operation “open” to do this? Is there a reason we couldn’t just cast down with the usual operators like we can with `Any`?

···

On Jan 4, 2016, at 11:43 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 4, 2016, at 6:24 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 3, 2016, at 10:44 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 3, 2016, at 9:14 PM, Drew Crawford <drew@sealedabstract.com <mailto:drew@sealedabstract.com>> wrote:

In this example, `<` requires two arguments of type Self. However, the `Comparable` existential, if allowed, would have Self bound to `Any`, not a concrete type. Therefore `<` would not be available and you would receive a compiler error on that line. It would be different than the current error and on a different line of code but it would still fail to compile.

With return types, you do not necessarily need to know the concrete type returned by the implementation. Swift could theoretically box the result itself into an existential and return that to the caller. I do not know whether this is the actual design that will be implemented or not.

It’s a reasonable direction that we’ve discussed in passing but never committed to.

  - Doug

All,

What do you feel are the appropriate next steps for this?

I’m trying to decide if I should target my energy into an initial formal proposal, experimenting to get a better idea for the details of an implementation by tinkering with the swift compiler, or focusing my energies elsewhere (either deferring or abandoning due to lack of community interest)

-DW

Since Hashable is only self constrained due to Equatable should we even allow this code?

let a: Equatable = 1
let b: Equatable = "2"

a == b

// in this case there could be a more general == function
func == <T: Equatable, U: Equatable>(x: T, y: U) -> Bool {
    if let yAsT = y as? T {
        return yAsT == x }
    return false
}

That is fun code :-) But I don’t understand what it has to do with Hashable

Since today equatable mandates a defined, public == method, and I think the generic is always loses to that specificity. You don’t even need dynamic casting, because your generic version only gets called when there isn’t a non-generic version.

// in this case there could be a more general == function
func == <T: Equatable, U: Equatable>(x: T, y: U) -> Bool {
    return false
}

1 == 1 // true
1 == “1” // false

Swift doesn’t seem to fight as hard as other languages against overloads that may be ambiguous in some contexts (for instance, two methods identical except for different return types). The flexibility of the compiler in interpreting code is one of the reasons that implicit conversions (such as with optionals or objective C bridging types) can lead to such confusing behavior in code. Most recent case to blow my mind,

let a = [[1]]
a == a // error: binary operator '==' cannot be applied to two '[Array<Int>]' operands

import Foundation

a == a // $R2: Bool = true

This works because the compiler promotes [[1]] to either [NSArray] or an NSArray outright to get to a working == operator.

But it seems like that there has to be a different implementation of `self` constrained functions (like ==) if a self constrained protocol is used without specifying `self`.

Thats what we mean by opening the type - exposing enough of the dynamic type information to the calling context such that we can safely expose methods. E.g. Something like this would be equivalent to your earlier fun code, except now the call site of the function is not generically constrained:

let a:Equatable = 1
let b:Equatable = “1”

func ==(lhs:Equatable, rhs:Equatable) -> Bool {
    typealias T = lhs.dynamicType
    let x = lhs as! T
    if let y = rhs as? T {
         return x == y
    }
    return false
}

a == b // false

Protocols would not have a simplistic order to implement (for example, I could be extending two parent protocols, both with their own associated types)

Do you mean conforming to a protocol by "extending"?
In this case it would be even more explicit about types:

Sorry, I used Java parlance where interfaces can extend other interfaces to add additional requirements/exposed capabilities.

The specific example I had in mind is CollectionType, which inherits from both SequenceType and Indexable. Would Index come before or after Generator and SubSequence if you were positionally declaring arguments. Should swapping the inheritance order on CollectionType be a fragile change?

To be clear, I'm proposing:
- Generic syntax for all types including protocols to be the same
- Named generic parameters eg: Array<Element: Int> , CollectionType<Element: String, Index: Int>

I’m not a fan of this syntax, because in an example where clause “where Element:Number" is a covariant constraint, and here (I think) you are using it as an invariant constraint. This seems confusing unless you specify Array<Element == Int>, etc.

You have the issue today that SequenceType does not expose Element but Generator. This kind of relationship can’t be expressed without a ‘where’ type syntax. I did sketch out a version of SequenceType that uses Element explicitly, so I assume this is what you are basing on. Still, such design can’t be assumed universal.

- Without name: Array<Int> , CollectionType<String, Int>

I already pointed out the CollectionType issue with positional arguments above. In addition, SequenceType will have an associated type of SubSequence, which I assume you are assuming would be the third argument and are implicitly stating “don’t care”.

I personally think there is a bit too much overlap between ‘where’ syntax, and possibly conflicting usage. In your example, would Index: Foo indicate that index is exactly Foo, is Foo or some subtype, or is a concrete implementation of Foo? If there were two syntaxes, I’d hope one to be a much closer shortening of the other.

Index: Foo
Index is of type Foo or a Subtype.

In that, the covariant case, Array<Element:Number> is _different_ than Array<Number> today.

Today, Array<Number> and Array<Int> are different concrete types. One deals in terms of Numbers, and one in terms of Ints. They are structured in memory different, etc.

When I say partial constraints, applied to generics in this case (reluctantly), I’m saying the following:

Implicitly define a protocol around all concrete Array instances where Element is defined to be Number or a subtype of Number, exposing all methods, properties, etc which are safe.

Safety rules are equivalency rules here: If a method or property returns Element, I can say that it is safe to represent the result of “var first:Element {get}” as a Number, because in the cases where it returns a subtype like Int, I can still safely upcast to Number. I have a safe and uniform interface.

If a method takes an argument or for any property setter, I have to require Element to be invariant. Modifying the first item of an array isn’t safe, because I might try to assign an Int value when really its a UInt8 array.

Subscripts unfortunately require both get and set, so even the getter has to be hidden currently if the type isn’t invariant. I’m not sure why the language requires this, TBH

That we are constraining the exposure of a protocol to only expose what is available by the associated type rules we specified is why I stayed away from terse, generic type syntax to start the proposal. Eventually, consensus might be that generic type form is better, but it adds an additional complexity to the understanding of the type model, which is the important part now in discussion. You are now using an existing syntax to refer to a new thing with new properties.

This is even more confusing if you partially constrain a generic type, because now Array<Number> (aka Array<where Element == Number> and Array<where Element:Number> have different properties (one is a protocol while the other is a concrete type, functionality is hidden because it is unsafe, possible boxing onto the heap and dynamic dispatch, etc)

I do have a mild personal conflict around whether exposing the ability to have partially constrained generics is a good idea, but I admit my hesitation is based on philosophies of good application and API design, and not based on language design.

implicit protocols on types seem pragmatic, because otherwise I’d have to create a protocol just for others to use me.

Allowing a protocol with associated types to be partially constrained seems pragmatic to me, since otherwise I have to use one of the workarounds above (hand-coded new protocol, generic constraints ever, and/or type-erasure struct-wrapper).

Expanding the usage of the implicit protocol on some generic type seems anti-pragmatic, just because it allows someone to go longer before they really evaluate whether locking consumers of their API into a single implementation is a good idea. They can always declare a protocol explicitly if they want partially constrained behavior.

That was also my concern since I want to keep the generic syntax consistent with protocols. So maybe we could make it the same in case of generic parameter names but without the "_" so there are no protocol like behaviors for generic types.

Even though it would be nice to have a covariant Array< _ > type.

Yep :-) I can’t speak for the core team, but from my perspective whether this proposal lives or dies depends on:

1. Implementability
2. Abiding by the Principal of Least Surprise

-DW

···

On Jan 22, 2016, at 9:36 AM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:
Am 22.01.2016 um 03:05 schrieb David Waite <david@alkaline-solutions.com <mailto:david@alkaline-solutions.com>>:

Inline

Since Hashable is only self constrained due to Equatable should we even allow this code?

let a: Equatable = 1
let b: Equatable = "2"

a == b

// in this case there could be a more general == function
func == <T: Equatable, U: Equatable>(x: T, y: U) -> Bool {
    if let yAsT = y as? T {
        return yAsT == x }
    return false
}

That is fun code :-) But I don’t understand what it has to do with Hashable

It's more an issue with Equatable which has self constraints.

Since today equatable mandates a defined, public == method, and I think the generic is always loses to that specificity. You don’t even need dynamic casting, because your generic version only gets called when there isn’t a non-generic version.

// in this case there could be a more general == function
func == <T: Equatable, U: Equatable>(x: T, y: U) -> Bool {
    return false
}

1 == 1 // true
1 == “1” // false

I thought also of this example:

let a: Equatable = 1
let b: Equatable = 2

a == b // would fail in your case

Swift doesn’t seem to fight as hard as other languages against overloads that may be ambiguous in some contexts (for instance, two methods identical except for different return types). The flexibility of the compiler in interpreting code is one of the reasons that implicit conversions (such as with optionals or objective C bridging types) can lead to such confusing behavior in code. Most recent case to blow my mind,

let a = [[1]]
a == a // error: binary operator '==' cannot be applied to two '[Array<Int>]' operands

import Foundation

a == a // $R2: Bool = true

This works because the compiler promotes [[1]] to either [NSArray] or an NSArray outright to get to a working == operator.

But it seems like that there has to be a different implementation of `self` constrained functions (like ==) if a self constrained protocol is used without specifying `self`.

Thats what we mean by opening the type - exposing enough of the dynamic type information to the calling context such that we can safely expose methods. E.g. Something like this would be equivalent to your earlier fun code, except now the call site of the function is not generically constrained:

let a:Equatable = 1
let b:Equatable = “1”

func ==(lhs:Equatable, rhs:Equatable) -> Bool {
    typealias T = lhs.dynamicType
    let x = lhs as! T
    if let y = rhs as? T {
         return x == y
    }
    return false
}

a == b // false

That's exactly what I wanted to do :)

Protocols would not have a simplistic order to implement (for example, I could be extending two parent protocols, both with their own associated types)

Do you mean conforming to a protocol by "extending"?
In this case it would be even more explicit about types:

Sorry, I used Java parlance where interfaces can extend other interfaces to add additional requirements/exposed capabilities.

The specific example I had in mind is CollectionType, which inherits from both SequenceType and Indexable. Would Index come before or after Generator and SubSequence if you were positionally declaring arguments. Should swapping the inheritance order on CollectionType be a fragile change?

To be clear, I'm proposing:
- Generic syntax for all types including protocols to be the same
- Named generic parameters eg: Array<Element: Int> , CollectionType<Element: String, Index: Int>

I’m not a fan of this syntax, because in an example where clause “where Element:Number" is a covariant constraint, and here (I think) you are using it as an invariant constraint. This seems confusing unless you specify Array<Element == Int>, etc.

You have the issue today that SequenceType does not expose Element but Generator. This kind of relationship can’t be expressed without a ‘where’ type syntax. I did sketch out a version of SequenceType that uses Element explicitly, so I assume this is what you are basing on. Still, such design can’t be assumed universal.

- Without name: Array<Int> , CollectionType<String, Int>

I already pointed out the CollectionType issue with positional arguments above. In addition, SequenceType will have an associated type of SubSequence, which I assume you are assuming would be the third argument and are implicitly stating “don’t care”.

I personally think there is a bit too much overlap between ‘where’ syntax, and possibly conflicting usage. In your example, would Index: Foo indicate that index is exactly Foo, is Foo or some subtype, or is a concrete implementation of Foo? If there were two syntaxes, I’d hope one to be a much closer shortening of the other.

Index: Foo
Index is of type Foo or a Subtype.

In that, the covariant case, Array<Element:Number> is _different_ than Array<Number> today.

Today, Array<Number> and Array<Int> are different concrete types. One deals in terms of Numbers, and one in terms of Ints. They are structured in memory different, etc.

When I say partial constraints, applied to generics in this case (reluctantly), I’m saying the following:

Implicitly define a protocol around all concrete Array instances where Element is defined to be Number or a subtype of Number, exposing all methods, properties, etc which are safe.

Safety rules are equivalency rules here: If a method or property returns Element, I can say that it is safe to represent the result of “var first:Element {get}” as a Number, because in the cases where it returns a subtype like Int, I can still safely upcast to Number. I have a safe and uniform interface.

If a method takes an argument or for any property setter, I have to require Element to be invariant. Modifying the first item of an array isn’t safe, because I might try to assign an Int value when really its a UInt8 array.

Why Element has to be invariant?

let uintArray: [UInt8] = [1]
var covariantArray: [Number] = uintArray
// this doesn't cause an error since Int is also a Number
covariantArray[0] = Int(36754)

Subscripts unfortunately require both get and set, so even the getter has to be hidden currently if the type isn’t invariant. I’m not sure why the language requires this, TBH

That we are constraining the exposure of a protocol to only expose what is available by the associated type rules we specified is why I stayed away from terse, generic type syntax to start the proposal. Eventually, consensus might be that generic type form is better, but it adds an additional complexity to the understanding of the type model, which is the important part now in discussion. You are now using an existing syntax to refer to a new thing with new properties.

This is even more confusing if you partially constrain a generic type, because now Array<Number> (aka Array<where Element == Number> and Array<where Element:Number> have different properties (one is a protocol while the other is a concrete type, functionality is hidden because it is unsafe, possible boxing onto the heap and dynamic dispatch, etc)

I don't think that this would be confusing since `where` indicates that the type is used in a more generic way.

You've convinced me. I wanted to keep the generic syntax simple but having thought over it your proposal it much better in handling more complex cases without introducing additional syntax.
Also considering Swifts type inference where types with long names are easier to use.

A "small" overview of several examples:

Using the former example of CollectionType which is used as a more concrete type (using a "leading dot" which is the same as `Self.`).

// These examples are only a demonstration of how you could use collection type

CollectionType<where .Generator.Element == String, .Index == Int, .SubSequence: Self>

CollectionType<where .Generator.Element == String, .Index == Int>

CollectionType<where .Generator.Element == String>

// if having `Element` as associated type
CollectionType<where .Element == String>

// covariance and invariance

protocol A {}
extension Int: A {}

let intArray: [Int] = [1, 2, 3]
let aArray: [A] = [1, 2, 3]

var col1: CollectionType<where .Element == A>
col1 = intArray // doesn't work since Int != A
col1 = aArray // obviously works

var col2: CollectionType<where .Element: A>
// both work since `Int` and `A` conform to `A` (currently in Swift 2.2 `A` does not conform to `A`, I don't know if this is a feature)
col2 = intArray
col2 = aArray

// with a concrete type using the example above:

// replace type of `col1` with (all replacements are equivalent)
`Array<A>` or `Array<where .Element == A>` or `[A]` or `[where .Element == A]`

// replace type of `col2` with (all replacements are equivalent)
`Array<where .Element: A>` or `[where .Element: A]`

// to be discussed: using many protocols together with protocol<>

// declaring a generic type T which is used in two protocols
protocol<where T, Self: SequenceType<where .Element == T>, Self: Indexable<where .Index == T>>

// although in this case it can be written as
SequenceType<where T, .Element == T, Self: Indexable<where .Index == T>>

Even though the latter one is shorter I'm skeptical about using `Self:` in protocol where clauses since at a first glance it implies that the type is only a `SequenceType`.

I do have a mild personal conflict around whether exposing the ability to have partially constrained generics is a good idea, but I admit my hesitation is based on philosophies of good application and API design, and not based on language design.

implicit protocols on types seem pragmatic, because otherwise I’d have to create a protocol just for others to use me.

Allowing a protocol with associated types to be partially constrained seems pragmatic to me, since otherwise I have to use one of the workarounds above (hand-coded new protocol, generic constraints ever, and/or type-erasure struct-wrapper).

Expanding the usage of the implicit protocol on some generic type seems anti-pragmatic, just because it allows someone to go longer before they really evaluate whether locking consumers of their API into a single implementation is a good idea. They can always declare a protocol explicitly if they want partially constrained behavior.

That was also my concern since I want to keep the generic syntax consistent with protocols. So maybe we could make it the same in case of generic parameter names but without the "_" so there are no protocol like behaviors for generic types.

Even though it would be nice to have a covariant Array< _ > type.

Yep :-) I can’t speak for the core team, but from my perspective whether this proposal lives or dies depends on:

1. Implementability
2. Abiding by the Principal of Least Surprise

-DW

Do you mean my proposal by "this proposal"?

Assuming you meant the proposal in general:

I think it can be implemented since the types are like generics in functions with where clauses. This proposal is almost only syntactic sugar.

I also think that it would not be surprising to offer this functionality because "where clauses" are quite well understood and I wouldn't count adding a new syntax as surprising.

- Maximilian

PS: Have you made a formal proposal already?

···

Am 22.01.2016 um 21:41 schrieb David Waite <david@alkaline-solutions.com>:

On Jan 22, 2016, at 9:36 AM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:
Am 22.01.2016 um 03:05 schrieb David Waite <david@alkaline-solutions.com>: