Probably a compiler bug: can't convert Range<Int> to Sequence where Element == Int

generics

(Vladimir Kelin) #1

I have a function declared like:

func iterateColumnsAlongGravity<S: Sequence>(using block: (_ indexes: S) -> ())
    where S.Element == Int

And when I call block with element of type Range<Int>, I'm getting the compiler error:

'Range<Int>' is not convertible to 'S'

There are also similar errors with similar types, which actually can be converted to S.

Please, see the full question on Stackoverflow.


(Ole Begemann) #2

This isn't a compiler bug. You are seeing an error because the caller of the function decides what type S has. Inside the function, you don't know the concrete type of S, only that it is some kind of Sequence with Int elements.

The way you wrote the function, there's no way for you to call block in its implementation because you don't have a value of type S and there's no way to construct one.


(Vladimir Kelin) #3

So, can I implement such a function using AnySequence? Or may be there other ways with generics? (Not casting everything to Array)


(Ole Begemann) #4

What are you trying to do? In your Stack Overflow question it seems you want to call the block with different types of sequences. You have two alternatives:

  1. Wrap the sequences in AnySequence<Int> and make that the argument type of block. This is easy, but doesn't have the best performance (but it's probably not a problem unless you use this sequence very frequently).

  2. Find a common sequence type that works for all branches in your switch statement. In your case, StrideTo, the return type of stride(from:to:by:) might fit the bill if you can eliminate the calls to reversed(), i.e. by striding from the max value to the min value in steps of -h. See this recent discussion for a similar problem.

In both cases, your function would not be generic. Its signature would be something like:

func iterateColumnsAlongGravity(using block: (_ indexes: AnySequence<Int>) -> ())

or:

func iterateColumnsAlongGravity(using block: (_ indexes: StrideTo<Int>) -> ())

(Vladimir Kelin) #5

My original idea was to use generics because I'm not interested in particular implementation details. I only need to know 2 things: 1) I can iterate 2) Elements are Int.
Am I understand generics wrong?


(Ole Begemann) #6

Generic parameters allow you to write functions that accept or return any type that satisfies some constraints. The important thing is that the caller of a generic function decides what concrete type to use for the generic parameter.

In your case, the implementor of the function (you) wants to tell the caller that they can expect some Sequence with Int elements, but you don't want to allow them to specify which type of sequence they get.

AnySequence<Int> expresses exactly what you want to say. I suggest you go with it unless you observe a performance problem. (By using AnySequence, you're still specifying a generic parameter (the <Int> part), but this time you're the caller of the AnySequence initializer, telling it which element type to use.)

Here are two recent threads that might interest you:


(Jordan Rose) #7

The original language feature you were looking for doesn't exist yet, but we could call it "generic closures":

// Made-up syntax:
func iterateColumnsAlongGravity(
    using block: <S: Sequence> (_ indexes: S) -> () where S.Element == Int)

Unfortunately, this has all sorts of other implications (can I make arrays of these? can other values be generic besides closures?) that make it a "big" feature, and probably not something that'll happen any time soon. But I did want to point out that it's a possible feature.

See also: Enum with generic cases


(Slava Pestov) #8

More precisely this is known as higher-rank polymorphism, for example see https://wiki.haskell.org/Rank-N_types.


(Joe Groff) #9

As they say, a closure is a poor man's object (and vice versa). You can approximate generic closures using a callback class or protocol with a generic method, for example:

class SequenceCallback<Element> {
  func run<S: Sequence>(on sequence: S) where S.Element == Element {
    fatalError("override me in a subclass")
  }
}

func iterateColumnsAlongGravity(using callback: SequenceCallback<Int>) { ... }

It's not as convenient to write a subclass of SequenceCallback as it would be to write a literal closure, of course, but anything can be expressed in this form that could be expressed with direct support for generic closures.


(Vladimir Kelin) #10

Why do you say that using AnySequence<Int> will have low performance?


(Ole Begemann) #11

See @Ben_Cohen's AnySequence benchmarks in the SE-0234: Remove Sequence.SubSequence proposal.

The performance impact of type erasure is pretty big, but note that this doesn't mean it would be problematic for your particular situation. You probably won't notice it if the sequences you deal with are relatively small and the code in question isn't executed in a tight loop.