Hello!
Check draft proposal, please.
Proposal
UPD:
[Proposal] Allow default values for parameters in generic clause
- 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
Introduction
Allow type parameters in generics clauses to have default values in class definition. Could be expanded to generic functions also.
Motivation
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 {
}
}
Question.
How to specify default values for type parameters for this queue?
Proposed solution
Prososed solution could solve problem in shorter and elegant way.
Proposed solution style
struct PriorityQueues {
class PriorityQueue<Container: Collection = Array<Int>, Comparison: BinaryCompareFunction = BinaryCompareFunctions.Less<Container.Element>> {}
}
Detailed design
There are several possible solutions for this functionality.
- Rule of last
- Naming parameters opposite to function style
- Naming parameters in function style
Rule of last
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).
Naming parameters.
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)
Underscored behavior
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.
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.
Mixing parameters examples.
For naming parameters solutions we could mix generic parameters and names by preserving parameter list order as function does with parameters list.
Underscored behavior example
class Foo<A, Second B = Y, C = Z> // valid
Foo<Int, Second: String, Int> // valid
Foo<Int, String, Int> // invalid or valid, tbd
Function behavior example
class Foo<A, _ B = Y, Third C = Z> // valid
Foo<Int, String, Third: Int> // valid
Foo<Int, String, Int> // invalid or valid, tbd
Source compatibility
Depends on solution.
Effect on ABI stability
Depends on solution.
Effect on API resilience
Depends on solution.
Alternatives considered
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
Naive
// MARK: Naive
extension PriorityQueues.test {
static func naive() {
let _ = PriorityQueues.PriorityQueue<Array<Int>, BinaryCompareFunctions.Less<Int>>(container: [])
}
}
Class method
// 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)
}
}
Subclass
// 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])
}
}
Class object
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()
}
}