Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save lolgear/fb071fa091b9f14a210724c329b2ebe2 to your computer and use it in GitHub Desktop.
Save lolgear/fb071fa091b9f14a210724c329b2ebe2 to your computer and use it in GitHub Desktop.
[Proposal] Allow default values for parameters in generic clause

[Proposal] Allow default values for parameters in generic clause

During the review process, add the following fields as needed:

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.

  1. Type of element T.
  2. Container type with elements matched Type T.
  3. 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.

  1. Rule of last
  2. Naming parameters opposite to function style
  3. 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.

  1. All parameters with default values are at the end of parameter list. ( At the rightside )
  2. 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.

  1. Naive
  2. Class method
  3. Subclass
  4. 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()
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment