- Proposal: SE-NNNN
- Authors: Dmitry Lobanov
- Review Manager: TBD
- Status: Awaiting Review
During the review process, add the following fields as needed:
- Implementation: apple/swift#NNNNN
- Decision Notes: Rationale, Additional Commentary
- Bugs: SR-NNNN, SR-MMMM
- Previous Revision: 1
- Previous Proposal: SE-XXXX
Allow type parameters in generics clauses to have default values in class definition. Could be expanded to generic functions also.
It is common in practise to have some generic class with several parameters. However, sometimes you need only specific subset of this class with several parameters set to concrete values.
Consider priority_queue from C++ stl.
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
It has several template parameters.
- Type of element T.
- Container type with elements matched Type T.
- Compare - binary function which will order elements in priority queue.
Lets try to implement similar priority queue in swift.
First, Consider comparison protocol.
protocol BinaryCompareFunction {
associatedtype T
static func compare(lhs: T, rhs: T) -> Bool
}
struct BinaryCompareFunctions {
struct Less<E>: BinaryCompareFunction where E: Comparable {
typealias T = E
static func compare(lhs: E, rhs: E) -> Bool {
return lhs < rhs
}
}
}
Next, define interface of priority queue. It will be nested in PriorityQueues structure for convenience.
// MARK: Definition
struct PriorityQueues {
class PriorityQueue<Container: Collection, Comparison: BinaryCompareFunction> where Container.Element == Comparison.T {
typealias Element = Container.Element
typealias ComparisonFunction = Comparison
var container: Container?
init(container: Container?) {
self.container = container
}
}
struct test {
}
}
How to specify default values for type parameters for this queue?
Prososed solution could solve problem in shorter and elegant way.
struct PriorityQueues {
class PriorityQueue<Container: Collection = Array<Int>, Comparison: BinaryCompareFunction = BinaryCompareFunctions.Less<Container.Element>> {}
}
There are several possible solutions for this functionality.
- Rule of last
- Naming parameters opposite to function style
- Naming parameters in function style
It may not affect current functionality. Consider simple class.
class Foo<A, B = Y, C = Z> {}
Rule of last could be splitted into statements.
- All parameters with default values are at the end of parameter list. ( At the rightside )
- There is no parameter without default value to the right of parameter with default value.
Consider invalid example.
class Bar<A, B = Y, C> {}
Thus, you could specify parameters only from left to right.
Foo<Int> // Foo<X, Y, Z>
Foo<Int, String> // Foo<Int, String, Z>
Foo<Int, String, Int> //Foo<Int, String, Int>
As you see there is no chance to save default value of second parameter (B = Y). It should be also specified to provide non-default value to third parameter (C = Z).
We could also add names for type parameters. It is the same functionality that functions have. However, it should be splitted into two possible solutions.
Functions have two types of parameters names: Inner parameter name and Outer parameter name.
func create(id: Int) // Inner parameter name == Outer parameter name
func mutate(_ id: Int) //underscored behavior. Parameter name is omitted in invocation
func destroy(byId id: Int) // Inner parameter name != Outer parameter name
// Usage:
create(id: 0)
mutate(0)
destroy(byId: 0)
Outer parameter is optional for generic clause. It will not break any existing functionality.
class Foo<A, B = Y, C = Z> // valid
class Foo<A, Second B = Y, Third C = Z> // valid
This solution is opposite to function behavior.
Outer parameter is required for generic clause. It will break all existing functionality.
class Foo<A, B = Y, C = Z> // invalid
class Foo<A, Second B = Y, Third C = Z> // valid
class Foo<A, _ B = Y, Third C = Z> // valid
This solution is similar to function behavior.
For naming parameters solutions we could mix generic parameters and names by preserving parameter list order as function does with parameters list.
class Foo<A, Second B = Y, C = Z> // valid
Foo<Int, Second: String, Int> // valid
Foo<Int, String, Int> // invalid or valid, tbd
class Foo<A, _ B = Y, Third C = Z> // valid
Foo<Int, String, Third: Int> // valid
Foo<Int, String, Int> // invalid or valid, tbd
Depends on solution.
Depends on solution.
Depends on solution.
There are several workarounds only, there is no convenient solution. Consider previous PriorityQueue. We could specify type parameters in several ways.
- Naive
- Class method
- Subclass
- Class Object
// MARK: Naive
extension PriorityQueues.test {
static func naive() {
let _ = PriorityQueues.PriorityQueue<Array<Int>, BinaryCompareFunctions.Less<Int>>(container: [])
}
}
// MARK: Class method
extension PriorityQueues.PriorityQueue {
class func defaultQueue<T: Comparable>(t: T) -> PriorityQueues.PriorityQueue<Array<T>, BinaryCompareFunctions.Less<T>> {
return PriorityQueues.PriorityQueue<Array<T>, BinaryCompareFunctions.Less<T>>(container: [])
}
}
extension PriorityQueues.test {
static func classMethod() {
let i: Int = 0
let _ = PriorityQueues.PriorityQueue<Array<Int>, BinaryCompareFunctions.Less<Int>>.defaultQueue(t: i)
}
}
// MARK: Subclass
extension PriorityQueues {
class PriorityQueue_DefaultLess<T: Comparable>: PriorityQueue<Array<T>, BinaryCompareFunctions.Less<T>> {
convenience init() {
self.init(container: [])
}
}
}
extension PriorityQueues.test {
static func subclass() {
let _ = PriorityQueues.PriorityQueue_DefaultLess<Int>()
}
}
extension PriorityQueues.test {
static func easy() {
let _ = PriorityQueues.PriorityQueue_DefaultLess(container: [0])
}
}
Object companion if I remember. Similar item in Scala.
// MARK: Class Object
extension PriorityQueues {
class PriorityQueueObject<T: Comparable> {
class func defaultQueue() -> PriorityQueue<Array<T>, BinaryCompareFunctions.Less<T>> {
return PriorityQueue<Array<T>, BinaryCompareFunctions.Less<T>>(container: [])
}
}
}
extension PriorityQueues.test {
static func classObject() {
let _ = PriorityQueues.PriorityQueueObject<Int>.defaultQueue()
}
}