0

Not quite sure how to phrase a good title for that ...

I'd like to define a generic protocol GBucket which is kinda like a set of items of the same type. A bit like a CollectionType, but with far less capabilities. Like so:

public protocol GBucket {

  associatedtype BElement

  func splitBucket
    <A: GBucket, B: GBucket where A.BElement == Self.BElement,
                                  B.BElement == Self.BElement>
    (idx: Int) -> ( A, B )
}

It essentially just provides a method to split a GBucket into two new GBuckets. Which can be of any type conforming to the protocol - i.e. the returned parts do not have to be the same class doing the split.

I tried that as a sample implementation:

 extension ArraySlice : GBucket {

  public typealias BElement = Generator.Element

  public func splitBucket
    <A: GBucket, B: GBucket where A.BElement == BElement,
                                  B.BElement == BElement>
    (idx: Int) -> ( A, B )
  {
    let ls : ArraySlice<A.BElement> = self[0..<idx]
    let a  : A = ls // this conversion FAILs
    return ( a, self[idx..<self.count] ) // this too, of course ;-)
  }
}

This produces:

Cannot convert value of type 'ArraySlice < Element >' to specified type 'A'

Which as far as I can tell should convert just fine. ArraySlice is a GBucket and the element type is the same thanks to the where specification.

And another, shorter sample illustrating the issue which doesn't use Array stuff:

public protocol GBucket {
  associatedtype BElement
  func otherBucket<A: GBucket where A.BElement == Self.BElement>() -> A
}

public class MyBucketType<T> : GBucket {
  public typealias BElement = T

  public func otherBucket<A: GBucket where A.BElement == BElement>() ->     A {
    return MyBucketType<A.BElement>()
  }
}

What me doin' wrong?

hnh
  • 13,957
  • 6
  • 30
  • 40

2 Answers2

0

Rasmus posted a proper link in a comment which explains nicely why the approach in the question fails: Swift Types - Returning constrained generics from functions and methods. He didn't amend his answer, hence I provide one - it is really his ;-)

The key point is that generics are resolved at the call site. The generic part is really more like a C macro. Conceptually there is nothing dynamic about it - it really is a "replace some generic type with an actual type".

Look at this:

extension ArraySlice : GBucket {

  public typealias BElement = Generator.Element

  public func splitBucket
    <A: GBucket, B: GBucket where A.BElement == BElement,
                                  B.BElement == BElement>
    (idx: Int) -> ( A, B )
  {
    let ls : ArraySlice<A.BElement> = self[0..<idx]
    let a  : A = ls // this conversion FAILs
    return ( a, self[idx..<self.count] ) // this too, of course ;-)
  }
}

The types A and B will be replaced with a type only when the splitBucket method is being called (or specialized by other means)! Example:

let aSlice : ArraySlice<Int> = myArray[1..<5]
let result : ( Array<Int>, Array<Int> ) = aSlice.splitBucket(3)

The types A and B in splitBucket would be expanded to Array<Int> only at this point. I.E. the specialized (generics expanded) version of the function would be:

public func splitBucket(idx: Int) -> ( Array<Int>, Array<Int> ) {
  let ls : ArraySlice<A.BElement> = self[0..<idx]
  let a  : Array<Int> = ls // this conversion FAILs
  return ( a, self[idx..<self.count] ) // this too, of course ;-)
}

This is why the let a has to fail.

P.S.: This doesn't answer how to accomplish the goal, but it is a starting point :-)

Community
  • 1
  • 1
hnh
  • 13,957
  • 6
  • 30
  • 40
-1

That ArraySlice conforms to GBucket and A conforms to GBucket does not mean that you can cast between them. You can cast from more specialised types to more general types, but not between two different more specialised types.

Maybe the code below could solve your problem?

public protocol GBucket : CollectionType {
    func splitBucket(idx: Int) -> ( Self, Self )
}

extension ArraySlice : GBucket {
    public func splitBucket(idx: Int) -> ( ArraySlice, ArraySlice )
    {
        let A = self[0..<idx]
        let B = self[idx..<self.count]      
        return (A, B)
    }
}
  • "You can cast from more specialised types to more general types" - as far as I can see I don't. A *is* specialised to `BElement` by the `where` clause to exactly the same type - which is precisely my intention. – hnh Apr 18 '16 at 12:36
  • Your answer lacks my requirement "the returned parts do not have to be the same class doing the split". I.e. This is supposed to be valid: 'extension _ArrayType : GBucket { func splitBucket() -> (ArraySlice, ArraySlice)'. That is, the split function can return any kind of `GBucket` as long as it matches the element type. With your approach, _ArrayType could only return _ArrayType, not ArraySlices. – hnh Apr 18 '16 at 12:42
  • The line: let a : A = ls fails, because A could be _any_ class conforming to the GBucket protocol. How would ArraySlice implement the methods and properties of A? – Rasmus Friis Kjeldsen Apr 18 '16 at 15:29
  • 1
    Ok, try to take a look at this: http://stackoverflow.com/questions/27404137/swift-types-returning-constrained-generics-from-functions-and-methods – Rasmus Friis Kjeldsen Apr 18 '16 at 16:56
  • That was a good pointer. In short: A is restricted to Bucket within the method, but the actual type (of a generic) is decided at the *call site*, it can't be changed within the method. Do you want to add/integrate this to your answer? – hnh Apr 18 '16 at 18:40
  • 27404137 explains why my approach fails. Is there another approach to accomplish what I want? The sample given in there doesn't really help me. With the new found knowledge I guess I need the 'collection' part of the GBucket be dynamic, while the 'element' part is generic :-) – hnh Apr 18 '16 at 19:15