2

Edit: I have restated and hopefully clarified this question over here. Now I've added the solution.

I've defined a function (see foo() in attached example) as a default function for structs adopting my protocol. It applies the + operator defined in respect of two other variables which themselves adopt other protocols and + is defined in one of those protocols. The variables are typed using associatedtypes. I get the message:

Binary operator '+' cannot be applied to operands of type 'Self.PointType' and 'Self.VectorType'

If I implement the function inside my struct (see bar() in attached) it works so I'm sure my + operator does work. My example is pared down to the minimum needed to work in a playground. Just remove the comments in the LineProtocol extension to get the error. It seems to me that Self.PointType is a Point and Self.VectorType is a Vector.

To be clear: The reason I used associatedtypes is because many different structs adopt each of the three protocols in the example so I can't name them directly

public protocol PointProtocol {
   associatedtype VectorType: VectorProtocol
   var elements: [Float] { get set }
}

extension PointProtocol {
   public static func +(lhs: Self, rhs:VectorType) -> Self {
      var translate = lhs
      for i in 0..<2 { translate.elements[i] += rhs.elements[i] }
      return translate
   }
}

public protocol VectorProtocol {
   associatedtype VectorType: VectorProtocol
   var elements: [Float] { get set }
}

public struct Point: PointProtocol {
   public typealias PointType = Point
   public typealias VectorType = Vector
   public var elements = [Float](repeating: 0.0, count: 2)

   public init(_ x: Float,_ y: Float) {
      self.elements = [x,y]
   }
}

public struct Vector: VectorProtocol {
   public typealias VectorType = Vector
   public static let dimension: Int = 2
   public var elements = [Float](repeating:Float(0.0), count: 2)

   public init(_ x: Float,_ y: Float) {
      self.elements = [x,y]
   }
}

public protocol LineProtocol {
   associatedtype PointType: PointProtocol
   associatedtype VectorType: VectorProtocol
   var anchor: PointType { get set }
   var direction: VectorType { get set }
}

extension LineProtocol {
//   public func foo() -> PointType {
//      return (anchor + direction)
//   }
}

public struct Line: LineProtocol {
   public typealias PointType = Point
   public typealias VectorType = Vector
   public var anchor: PointType
   public var direction: VectorType

   public init(anchor: Point, direction: Vector) {
      self.anchor = anchor
      self.direction = direction
   }

   public func bar() -> Point {
      return (anchor + direction)
   }
}

let line = Line(anchor: Point(3, 4), direction: Vector(5, 1))
print(line.bar())
//print(line.foo())

Solution adapted from @Honey's suggestion: replace extension with:

extension LineProtocol where Self.VectorType == Self.PointType.VectorType {
   public func foo() -> PointType {
      // Constraint passes VectorType thru to the PointProtocol
      return (anchor + direction)
   }
}

Tchelyzt
  • 161
  • 1
  • 10
  • Just as a side note: Your naming of types is SUPER confusing. `PointProtocol`, `PointType`, `Point`. If you look at Apple's API design, they try to limit similar names. If they don't have distinct purposes then _maybe_ you're doing something wrong – mfaani Nov 05 '19 at 15:26
  • Sorry, it was not intended to confuse. The framework I'm building is for geometry and works on points, vectors, matrices, lines, planes, etc. In each case there are many sub-types but struct doesn't support inheritance so I use protocols. So, for example, the Point family has one protocol (PointProtocol) and several structs (Point2D, Point3D, Point4D, etc) and to know which I'm referring to I provide an `associatedtype` PointType. The same structure is used for other families of geometric object. That's all there is to understand the naming. – Tchelyzt Nov 05 '19 at 16:04

1 Answers1

1

I know what the problem is. Not sure if my solution is the best answer.

The problem is that both your associatedtypes have associatedtypes themselves.

So in the extension, the Swift compiler can't figure out the type of the associatedtypes — unless you constrain it.

Like do:

extension LineProtocol where Self.VectorType == Vector, Self.PointType == Point {
    public func foo() -> Self.PointType {
      return (anchor + direction)
   }
}

Your code works for your concrete type Line, because both your associatedtypes have their requirements fulfilled ie:

public typealias PointType = Point // makes compiler happy!
public typealias VectorType = Vector  // makes compiler happy!

FWIW you could have got rid of the explicit conformance to your associatedtype requirements and let the compiler infer1 conformance to your associatedtypes requirements and write your Line type as such:

public struct Line: LineProtocol {

   public var anchor: Point
   public var direction: Vector

   public init(anchor: Point, direction: Vector) {
      self.anchor = anchor
      self.direction = direction
   }

   public func bar() -> Point {
      return (anchor + direction)
   }
}

1: Generics - Associated Types

Thanks to Swift’s type inference, you don’t actually need to declare a concrete Item of Int as part of the definition of IntStack. Because IntStack conforms to all of the requirements of the Container protocol, Swift can infer the appropriate Item to use, simply by looking at the type of the append(_:) method’s item parameter and the return type of the subscript. Indeed, if you delete the typealias Item = Int line from the code above, everything still works, because it’s clear what type should be used for Item.

mfaani
  • 33,269
  • 19
  • 164
  • 293
  • Hi Honey. thanks for the speedy reply. However, it won't solve the problem because the reason I'm using a protocol is that there are many `struct`s which conform to each of Vector-, Point- and Line-Protocols so I need to pass in the specific one trying to use it. For instance, a line in 2-Dimension cannot be added to a Point in 3-D. Effectively there are Point2, Point3, Vector3 structs and so on ... – Tchelyzt Nov 05 '19 at 15:06
  • Understood. Was the description of the problem and my not-good-solution clear to you? – mfaani Nov 05 '19 at 15:12
  • I think you're probably right to say that it the associatedtype in both protocols that causes the problem but I can't see why – Tchelyzt Nov 05 '19 at 15:16
  • I don't use associatedtypes at all @hamish can explain better. But, `associatedtype PointType: PointProtocol` and the line after are the problem. Once in your extension, the compiler will look around. It will see that `anchor` is of type `PointProtocol`, So it will look into that type, right then the compiler is like "hold it right there! not so fast. `PointProtocol` also has an `associatedtype`, what is the type of that? `PointProtocol` Ok! Let me look into that. Bummer that also has an associatedtype named `VectorType` What type do you want it to be" <— what is your answer to that question? – mfaani Nov 05 '19 at 15:27
  • So following the same kind of thinking, I renamed the associated types in `LineProtocol` to avoid that kind of conflict but that didn't help. Actually anchor will have a specific type (say a Point as in the example) and Point's tell PointProtocol's just what VectorType they require (say a Vector as in the example) so there should be no ambiguity. Notice that the Bar() function has no difficulty resolving types. – Tchelyzt Nov 05 '19 at 15:47
  • The suggestion to rename was so it becomes easier for others to read. It won't resolve the error. I've asked Hamish to see if he can take a look. Let's just wait. I don't have any other input. Sorry – mfaani Nov 05 '19 at 15:50
  • `public typealias PointType = Point` (in your struct) vs. `associatedtype PointType: PointProtocol` (in your protocol). They are NOT the same to the compiler. Is that clear to you? If not let me know. In your struct it's clear because you're using a **concrete** type of `Point`. In your protocol it's not clear to the compiler (until you use a concrete type), hence not in your extension. – mfaani Nov 05 '19 at 16:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201919/discussion-between-tchelyzt-and-honey). – Tchelyzt Nov 06 '19 at 00:23
  • your proposed solution is pretty close to correct. It turns out that only `direction` is a problem and the constrain needs to be on `Self.PointType.VectorType`. Well done and thanks – Tchelyzt Nov 07 '19 at 14:44