Why does type inference break down here?

Hi,

As part of an API I'm working on, I need to write some data to a buffer held by another object. The source of the data can vary depending on the code path, and I may need to break the write up in to multiple smaller writes. Here's the solution I came up with:

struct Foo {
  func writeComponent<T>(
    _ writer: ((T) -> Void) -> Void
  ) where T: Collection, T.Element == Int {
    var count = 0
    writer { contentToWrite in
      // write content
      count += contentToWrite.count
    }
    print(count)
  }
}

let x = Foo()
if Bool.random() {
  x.writeComponent { writer in
    writer([1, 2, 3])
  } // Prints "3"
} else {
  x.writeComponent { writer in
    writer(0..<100)
  } // Prints "100"
}

Perhaps a bit unconventional, but it works well. I'm able to write multiple times in each call to writeComponent, which means I can, say, parse my data and skip some elements, etc. All the while, Foo is able to gather statistics (such as the number of elements written as part of this component), and gets an opportunity at the end to do any final touching up.

Unfortunately, when I write multiple times, I lose type inference. I can accept that - I guess that this kind of constraint system might not be solvable, or not in reasonable time at least. On the other hand, it is a little bit disappointing - we've established that it can solve the first call to writer just fine, and that solution would have worked for every other call. Why doesn't it even try a guess?

  x.writeComponent { (writer: ([Int]) -> Void) in
    writer([1, 2, 3])
    if ... {
      writer([4, 5, 6])
    }
    // ...etc
  } // Prints "6"

But what bothers me more is that I also lose type inference for single writes, if the return type is not Void:

struct Foo {
  func writeComponent<T>(
    _ writer: ((T) -> Void) -> Int // <- Returns Int now
  ) where T: Collection, T.Element == Int {
    // ...
  }
}

let x = Foo()
x.writeComponent { writer in // Error: generic parameter 'T' could not be inferred
  writer([1, 2, 3])
  return 42
}

x.writeComponent { (writer: ([Int]) -> Void) in // Works if explicit
  writer([1, 2, 3])
  return 42
}

This one I don't understand at all. The part of the closure involving T hasn't changed from the first example, but somehow type inference can't handle it now? Why does this happen?

Is this not simply because single-line closures have type-inference, and multi-line closures do not?

3 Likes

Ohhhhh... yeah, that sounds like it could be the reason. I wasn’t aware of that limitation, thanks!

1 Like

A trick for restoring type inference is to abuse a tuple:

let x = Foo()
x.writeComponent { writer in
  (writer([1, 2, 3]), 42).1
}
5 Likes