[swift-evolution] [Pre-proposal] Use of angle bracket type generics with protocols
Ross O'Brien
narrativium+swift at gmail.com
Fri Feb 26 11:23:54 CST 2016
I've been using generics a fair amount recently. The Swift 3 notes declare
an intention for 'complete generics', but there isn't much information on
what this consists of.
I think there are three key elements to the terminology here. In a
declaration of a generic type, you have associated types, generic
parameters, and generic constraints. I imagine the Swift for a Set type
looks like this:
struct Set<Element where Element : Hashable> : CollectionType
{
typealias Index = SetIndex<Element>
}
Element is a generic parameter. Element : Hashable is a generic constraint.
Index is an associated type and it's required to conform to CollectionType.
The distinction is important; we could create a non-generic type IntSet
like this:
struct IntSet : CollectionType
{
typealias Index = SetIndex<Int>
}
I think it should be possible to declare a property which conforms to
CollectionType, constrained such that its Index is a SetIndex<Int> type (as
opposed to, say, SetIndex<String>). Or, I think it should be possible to
declare a property which conforms to GeneratorType, constrained that its
Element conforms to IntegerArithmeticType, so I can generate random numbers
and add them together and not yet decide whether they're Ints or Doubles or
whether the generation was by dice or playing cards.
I think I should be able to declare a Graph generic data structure with
values for its nodes and edges - e.g. a Travelling Salesman Problem graph
of Graph<City, Route> - and have a matching generic GraphTraverser type
which takes a graph and a starting node, and traverses the graph reporting
to a matching GraphTraversalDelegate type when it traverses an edge,
reaches a node, or needs a decision making on which of the available edges
it should traverse next. For this, the GraphTraverser should have a
matching 'generic parameter signature' to the Graph, but the
GraphTraversalDelegate will be context specific and should dictate
type-safe decisions based on City nodes and Route edges.
At the moment, this traversal pattern isn't possible because protocols
can't have generic parameters. I've seen programmers develop 'thunk' types
to work around this, but this should be neater.
So, I think it's important that we keep generic parameters and associated
types distinct - but, protocols should be able to handle generics, perhaps
with some implicit typealiasing.
The OP's examples; the developer would write this:
protocol FooType<Element, Index where Index : ForwardIndexType> {
func getElement(atIndex:Index) -> Element?
}
struct Foo<Element> : FooType<Element, Int> {
func getElement(atIndex:Index) -> Element? { ... }
}
But what would result would be this:
protocol FooType<Element, Index where Index : ForwardIndexType> {
typealias Element // implicitly generated associatedtypes and typealiases
typealias Index // implicit
func getElement(atIndex:Index) -> Element?
}
struct Foo<Element> : FooType {
typealias Element = Element // implicitly generated associatedtypes and
typealiases
typealias Index = Int // implicit
func getElement(atIndex:Index) -> Element? { return nil }
}
I'm not sure what I'd prefer for the syntax of a type which e.g. conformed
to FooType and whose Index was an Int. One of these two?
1) generic parameters matched by position in sequence
let foo : FooType<_, Int> = Foo<String>
2) associated type matched to type.
let foo : FooType<Index:Int> = Foo<String>
Here's a further code example - the graph traverser from before might look
like this:
struct GraphTraverser<NodeType, EdgeType>
{
let graph : Graph<NodeType, EdgeType>
var currentNode : NodeType
weak var delegate : GraphTraversalDelegate<NodeType, EdgeType>?
func move()
{
let options = graph.edgesFromNode(currentNode)
if let decision = delegate?.graphTraverser(self,
chooseEdgeFromOptions:options)
{
currentNode = decision.destination
delegate?.graphTraverser(self, didTraverseEdge:decision)
delegate?.graphTraverser(self, didReachNode:currentNode)
}
}
}
protocol GraphTraversalDelegate<NodeType, EdgeType>
{
// implicit typealiasing
func graphTraverser(graphTraverser:GraphTraverser<NodeType, EdgeType>,
didTraverseEdge edge:EdgeType)
func graphTraverser(graphTraverser:GraphTraverser<NodeType, EdgeType>,
didReachNode node:NodeType)
func graphTraverser(graphTraverser:GraphTraverser<NodeType, EdgeType>,
chooseEdgeFromOptions options:[EdgeType]) -> EdgeType?
}
struct TravellingSalesman : GraphTraversalDelegate<City, Route>
{
// implicit typealiasing; all NodeTypes are City, all EdgeType are Route
var citiesVisited : Set<City>
var distanceTravelled : Int
mutating func graphTraverser(graphTraverser:GraphTraverser<City, Route>,
didTraverseEdge edge:Route)
{
distanceTravelled += edge.distance
}
mutating func graphTraverser(graphTraverser:GraphTraverser<City, Route>,
didReachNode node:City)
{
citiesVisited.insert(node)
graphTraverser.move()
}
func graphTraverser(graphTraverser:GraphTraverser<City, Route>,
chooseEdgeFromOptions options:[Route]) -> Route?
{
return Random(options.filter{ route in
!citiesVisited.contains(route.destination) })
}
}
Comments?
On Fri, Feb 26, 2016 at 11:22 AM, Haravikk via swift-evolution <
swift-evolution at swift.org> wrote:
> One of the big things that bugs me about working with protocols and
> generics is that they have a fundamentally different style to working with
> generics on structs and classes. While has some minor benefits on
> differentiating them, I think that overall it results in inconsistency that
> makes them harder to work with.
>
> I’d like to propose two fairly minor changes that allow protocols to be
> defined using angle brackets for generics to make things much easier.
>
> First is allowing a protocol to be defined using angle brackets for its
> generic type(s) like so:
>
> protocol FooType<Element, Index:ForwardIndexType> {
> func getElement(atIndex:Index) -> Element?
> }
>
> In this example the protocol Foo has implicit type aliases for Element and
> Index, which can be fulfilled like so:
>
> struct Foo<Element> : FooType<Element, Int> {
> func getElement(atIndex:Index) -> Element? { … }
> }
>
> The other important case is defining type constraints with protocol
> generics, currently we have to do stuff like this:
>
> func append<S:SequenceType where S.Generator.Element ==
> Element>(contentsOf theSequence:S) { … }
>
> However, while definition with a where clause is powerful, in the majority
> of cases it’s much more complex than it needs to be. So I’d like to propose
> that we can have the following:
>
> protocol SequenceType<Element> { … }
> func append(contentsOf theSequence:SequenceType<Element>) { … }
>
> Behind the scenes these may be unwrapped into type aliases and where
> clauses like we have now, but for the developers this is much, much simpler
> to work with, especially when most generic constraints are pretty simple.
>
> I’m not proposing the removal of where clauses as they can be really
> useful in more unusual cases, so will still have utility there. I’m not so
> sure about explicit type alias declaration like we have now though; using
> the angle brackets to declare these seems just as capable to me unless
> there are some cases I haven’t considered.
>
>
> This seems like something that may have been discussed in the past, but
> I’m not that great on specific terminology for features (I’m not as well
> versed as others are on the correct names for language features) so I
> haven’t found anything, but fully expect that I may have missed it somehow,
> in which case a link would be appreciated.
>
> But if no proposal currently exists then I’d love to know, as I could try
> to create a more formal proposal for this if necessary, as I know that I
> for one would benefit greatly from simplified protocol generics, as they’re
> currently one of my least favourite features to use syntactically, but one
> I need to use a lot.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160226/a73c0d5d/attachment-0001.html>
More information about the swift-evolution
mailing list