How do you forward operators to related types? (e.g. CGPoint, CGVector, SIMD2)

My actual question is bigger than that, but operators are the bulk of the practical problem.

Use case: There are a bazillion things you might want to do with 2-vectors. And there isn't a single  standard for them, even when their scalars are Doubles. We've got CGPoint, CGVector, CGSize, and probably a bunch I don't know about. You could define a protocol like this…

/// A type that can operate with other types via intermediate conversion.
public protocol CommonOperable {
  /// The type to be converted to, for interoperability.
  associatedtype Operand

  init(_: Operand)
  var convertedToOperand: Operand { get }
}

extension CommonOperable {
  /// Forwards  operators to converted operands.
  static func operate<Operable1: CommonOperable, Result: CommonOperable>(
    _ operable0: Self,
    _ operate: (Operand, Operand) -> Operand,
    _ operable1: Operable1
  ) -> Result
  where Operand == Operable1.Operand, Operand == Result.Operand {
    Result(
      operate(
        operable0.convertedToOperand,
        operable1.convertedToOperand
      )
    )
  }
}

…and define all of their Operands to be SIMD2<CGFloat.NativeType>. With that, you could save some duplication…

public extension CommonOperable
where Operand: SIMD, Operand.Scalar: FloatingPoint {
  static func + <Operable1: CommonOperable, Result: CommonOperable>
  (operable0: Self, operable1: Operable1) -> Result
  where Operand == Operable1.Operand, Operand == Result.Operand {
    operate(operable0, +, operable1)
  }

  static func + (operable0: Self, operable1: Self) -> Self {
    operate(operable0, +, operable1)
  }

  static func += <Operable1: CommonOperable>
  (operable0: inout Self, operable1: Operable1)
  where Operand == Operable1.Operand {
    operable0 = operable0 + operable1
  }

//MARK:-

  static func - <Operable1: CommonOperable, Result: CommonOperable>
  (operable0: Self, operable1: Operable1) -> Result
  where Operand == Operable1.Operand, Operand == Result.Operand {
    operate(operable0, -, operable1)
  }

  static func - (operable0: Self, operable1: Self) -> Self {
    operate(operable0, -, operable1)
  }

  static func -= <Operable1: CommonOperable>
  (operable0: inout Self, operable1: Operable1)
  where Operand == Operable1.Operand {
    operable0 = operable0 - operable1
  }

//MARK:-

  //...😐
}

…but that's still terrible. What's better? Ideally, all those CG types could be SIMDs, but I don't think that's the way?

1 Like

If they were all the same type under the hood, you'd lose some typechecking benefits (admittedly, sometimes this seems like a hassle, but it also has some real benefits).

Points and vectors look similar, but as mathematical formalisms, they are different things and should have different operations (points--in the CGPoint sense--are elements of an affine space, which means, among other things, that you can't multiply a point by a scalar or add two points, while vectors have addition and scalar multiplication). The CGPoint and CGVector types don't expose most of these operations (partially because of their history as Objective-C types), but if they did, you wouldn't want them to provide the same set of operations, and you certainly wouldn't want them to interoperate¹. The typechecker should produce an error if you add two CGPoints, just like it should be a type error if you call the bark method on a Cat object.

¹ except possibly for subtracting two points to get a vector, and adding a vector to a point (this is the group action of the vector space on the affine space, and is often written as addition, though it's probably better to model it as a transform being applied: vector.act(point) or point.translate(by: vector)).

2 Likes

To add, given the semantic, you’d want to transform CGPoint with CGAffineTransformation using applying(_:) instead of directly applying vector arithmetic. I still wish there’s point + size -> point though.

1 Like

Just imagine only CGVector is CommonOperable then.

I am not looking for feedback on whether all vectors of the same length are interchangeable.