Default parameter on init's should satisfy other required init's

There is not much to say here, this is an issue I just encountered now, where I need two different but very similar inits, but for some reason this seems to be not supported yet.

Here is a code snippet that forced me to create required convenience override init which in my honest opinion just ugly. Instead it would be far better if default parameter on inits could satisfy other required inits.

protocol A {
  init(foo: String)

protocol B {
  init(foo: String, closure: (Self) -> Void)

class Super {
  let foo: String
  init(foo: String) { = foo

final class Sub : Super, A, B {
  // This is simplified version of the init and does not mirror 
  // a full real world implementation
  /* required */ init(foo: String, closure: (Sub) -> Void) {
    super.init(foo: foo)

  // Satisfy `A` protocol which collides with the designated 
  // initializer.  To not to implement the same init over again
  // without the closure, we can add `convenience` and 
  // re-route it to the current designated initializer
  /* required */ convenience override init(foo: String) {
    self.init(foo: foo, closure: { _ in })

Ideally I'd wish I could just create a single init to satisfy both protocols:

  • required init(foo: String, closure: (Sub) -> Void = { _ in })

Without commenting on the rest of your post, why are you using required in a final class?

That's a habit of mine I guess, but I'll drop final in the example, so that the issue remains.

Doesn’t compile now, because the closure has a Self requirement.

1 Like

Fixed, commented required out and re-added final. The issue main issue remains. Thank you for pointing out that it stopped compiling.

It seems like there was a similar discussion before:

@Karl want to take over and drive it forward?

This problem with the way default arguments are handled keeps coming up! If:

init(x: Int = 0) { ... }


init() { self.init(x: 0) }
init(x: Int) { ... }

Then the problem would be solved.

That doesn't scale, unfortunately. As soon as you have more than one default argument, you have to account for all the possibilites, which means a lot of extra code. But I don't think that makes Adrian and Karl's proposals unreasonable.

I don’t think this is a problem in practice. You don’t have to do every order since you can’t change the order of arguments. Also there are few methods with lots of defaults.

You can still omit them in any order, which means there's still a quadratic number of entry points generated.

func test(a: Int = 1, b: Int = 2) { print(a, b) }

test(a: 0)
test(b: 0)
test(a: 0, b: 0)

Isn't it exponential? As in 2^defaults entry points

1 Like

Oops, yes, you're correct. "Presence or absence" is a bit, so N "presence or absence"s is N bits, i.e. 2^defaults.

Functions with, let's say, > 5 default values for arguments are quite rare after all.

But calling a function means invoking a block by passing some arguments. If default values or information for instantiating them were stored together with the signature, we could 'substitute' the defaults with the passed arguments where needed upon a call and then invoke, without needing to generate functions for all the possible entry points. Is this an ill approach?

The feature is available in other languages, e.g. Scala:

trait Parent {
    def doSomething(s:String = "parent") = s

class Child extends Parent {
    override def doSomething(s:String = "child") = s

val p: Parent = new Child()
p.doSomething() // child

The nearest equivalent in Swift, using classes because protocols can't do defaults, would print parent :frowning:.

The Scala implementation is something like:

trait Parent {
    var doSomthingDefaultSArgument = "parent"
    def doSomething(s:String) = s

class Child extends Parent {
    override var doSomthingDefaultSArgument = "child"
    override def doSomething(s:String) = s

val p: Parent = new Child()
p.doSomething(p.doSomthingDefaultSArgument) // child

That way they don't generate extra methods. However as discussed above it isn't going to be many extra methods and it is much simpler to catch the edge cases like:

trait Trick {
  def trick() = "trick" // Note no argument

class Treat extends Trick {
  override def trick(s:String = "treat") = s // Override trick using a default argument

Which incidental Scala fails with.