4

Yesterday I asked a question. This is an attempt to state the question more clearly.

THE CONTEXT:

I have a family of structs (called Vector2D, Vector3D, etc) which represent vectors in various dimensions. Their implementations are very similar but since inheritance is not available for structs I use a protocol (VectorProtocol) which enables me to write default member functions as protocol extensions instead of repeating them for each struct.

Similarly, I have a family of structs (called Point2D, Point3D, etc) which represent points in various dimensions. Their implementations are also very similar so I use another protocol (PointProtocol) for the same reason.

In each case the protocols use associatedtypes to identify the particular struct instance type.

Sometimes PointxDs and VectorxDs interact so PointProtocol also has an associatedtype to identify the particular VectorxD it needs to handle and VectorProtocol has an associatedtype to identify the particular PointxD it needs to handle.

So far, everything works fine, including interaction between points and vectors. For instance the difference between 2 points is a vector and the sum of a point and a vector is a point and I can define operators once to handle these cases for every dimension as follows:

public protocol PointProtocol {
    associatedtype VectorType: VectorProtocol
    /*    etc.  */
}

extension PointProtocol {
    public static func +(lhs: Self, rhs: VectorType) -> Self { /*…*/ }
    public static func -(lhs: Self, rhs: Self) -> VectorType { /*…*/ }
}

THE PROBLEM:

Now I want to add a further family of structs (called Line2D, Line3D, etc) which represent lines in various dimensions and which interact with points and vectors. I thought I was doing more of the same but there is a subtle difference. Lines are composed of points and vectors.

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

public struct Line2D: LineProtocol {
    public typealias PointType = Point2D
    public typealias VectorType = Vector2D
    var anchor: PointType
    var direction: VectorType
    public init(anchor: Point2D, direction:Vector2D) { … }
}

This causes no problem when it comes to constructing, for example, a Line2D using a Point2D and a Vector2D. However, the compiler baulks when it comes to this default declaration:

extension LineProtocol {
    public func endOfLineSegment() -> PointType {
        return (anchor + direction)    // Compiler ERROR
            // Binary operator '+' cannot be applied to operands of type
            // 'Self.PointType' and 'Self.VectorType'
    }
}

It looks like the compiler cannot find the operator declaration public static func +(lhs: PointType, rhs: VectorType) -> PointType even though anchor is clearly of type PointProtocol and direction is clearly of type VectorProtocol. So I think endOfLineSegment() knows that anchor and direction are PointType and VectorType respectively, which means it should know that they are PointProtocol and Vector Protocol too, but it does not know how to set the two associatedtypes for these protocols.

Does anybody know how to fix this? (Without having to implement the function individually in each struct declaration which does work)

Below find sufficient minimal code to generate the error in a playground

public protocol PointProtocol {
   associatedtype PointType: 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())
Tchelyzt
  • 161
  • 1
  • 10

1 Answers1

0

It turns out it was only the VectorType causing the problem and it could be solved by adding this constraint!!

extension LineProtocol where Self.VectorType == Self.PointType.VectorType {
    // Constraint passes VectorType thru to the PointProtocol
    public func endOfLineSegment() -> PointType {
        return (anchor + direction)
    }
}
Tchelyzt
  • 161
  • 1
  • 10