[Pitch] Allow default parameters in generic class definition

Allow default parameter in generic class definition

Several days ago I wrote a post about default parameters in generic class definition. It is not possible yet, but some techniques could emulate this behavior.

Simple task: implement priority queue from c++ stl.

Let's look at priority_queue from Cplusplus STL.

template <class T, class `Container` = vector<T>,
  class Compare = less<typename Container::value_type> > class priority_queue;

It has three 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.

So, here we have three parameters, some of them could be omitted in case of default assumptions how this was done in c++ stl.
Default container value is vector<T>.
Default compare class is less with elements from container type.

I will try to implement similar functionality for priority queue in swift.

First, a definition of protocol and built-in comparison functions.

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, a definition of priority queue with several parameters ( Element parameter omitted in case of simplicity ).

// 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 {

    }
}

There are several possible alternatives to specify generics parameters that I know:

  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()
    }
}

Most of them are clumsy.
It would be nice to have a mechanism similar to templates of c++ or Scala ( which makes it possible to set default type parameter in generics clauses ).

1 Like

It is worth noting that one of the problems here is that, having n parameters with m <= n out of them having default values, the only unambiguous cases are when you provide n or n - m parameters at initialization.

Well, in special cases this might change, i.e. constraints, but not in general.

Sure, it could be solved, for example, in rule of last or naming parameters.
Rule of last means that only last parameters could have default values. Flip-flop from default to non-default behavior is not accepted.

// Allowed.

class <First: A, Second: B, Third: C = ConcreteType> {}

// Not Allowed

class <First: A, Second: B = ConcreteType, Third: C> {}

Or it could be done by naming parameters. It could be convenient to read parameter names.

In my example above:

  let _ = PriorityQueues.PriorityQueue<Container: Array<Int>, Comparison: BinaryCompareFunctions.Less<Int>>()

Increase the number of parameters with defaults:

class Foo<A, B = Type1, C = Type2> { ... }

let foo =  Foo<Int, Bool> // Foo<Int, Bool, Type2> or Foo<Int, Type1, Bool> 

Naming parameters is a solution, but I'm not sure it's convenient for Swift since it extends things quite a bit.
If so, they should be optional of course.

Parameters would be filled from left to right.

class Foo<A, B = Type1, C = Type2> { ... }

let foo =  Foo<Int, Bool> // Foo<Int, Bool, Type2>

let bar = Foo<Int, Bool, Int> // Foo<Int, Bool, Int>

Generics could use solution of naming parameters from functions. But by default all parameters has 'underscoded' behavior e.g.

static func object(_ t: String? = nil) {}

object("") // valid

Then how would you leave the second parameter default if you wanted to? :)

Yeah, that sounds alright.

You can't leave middle parameters with default value if you want to change parameter at the end of list.

That is how Ruby implementation works ( if I am correct ).

1 Like

I've often reached for this syntax, only to find it's not supported. It would solve the problem of which order defaults should be filled in.

typealias Foo = Foo<Bool>
typealias Foo<A> = Foo<A, Int>
typealias Foo<A, C> = Foo<A, String, C>
class Foo<A, B, C> { … }