I'm trying the following code where i'm trying to overload "+" operator with lhs and rhs of different types and returning a new type as a return value.
import Foundation
struct e {
let index:Int
}
extension e {
init(_ x:Int) {
index = x
}
}
struct GeometricNumber {
let e:e
let coefficient:Float
}
extension GeometricNumber : CustomDebugStringConvertible {
var debugDescription: String {
"\(coefficient)*e(\(e.index))"
}
}
precedencegroup eProccessOrder {
associativity:left
higherThan: AdditionPrecedence
}
infix operator *:eProccessOrder
func * (_ coeff:Float, _ es:e) -> GeometricNumber {
GeometricNumber(e: es,coefficient: coeff)
}
func * (_ es:e,_ coeff:Float) -> GeometricNumber {
GeometricNumber(e: es,coefficient: coeff)
}
enum MathOperations {
case addition,substraction,multiplication,division
}
struct GeometricExpression {
var expression:[(GeometricNumber, MathOperations)]
}
extension GeometricExpression:CustomDebugStringConvertible {
var debugDescription: String {
return self.expression.map { (first:GeometricNumber, second:MathOperations) -> String in
return "\(first) \(operation(second))"
}.joined(separator: " ")
}
}
func operation(_ op:MathOperations) -> String {
switch op {
case .addition:
return "+"
case .substraction:
return "-"
case .multiplication:
return "*"
case .division:
return "/"
}
}
precedencegroup geometricExpressions {
associativity:left
}
infix operator +:geometricExpressions
func + (_ lhs:GeometricNumber, _ rhs:GeometricNumber) -> GeometricExpression {
var exp = [(GeometricNumber, MathOperations)]()
exp.append((lhs,.addition))
exp.append((rhs,.addition))
return GeometricExpression(expression: exp)
}
func + (_ lhs:GeometricExpression, _ rhs:GeometricNumber) -> GeometricExpression {
var exp = [(GeometricNumber, MathOperations)]()
exp.append(contentsOf: lhs.expression)
exp.append((rhs, .addition))
return GeometricExpression(expression: exp)
}
func + (_ lhs:GeometricNumber, _ rhs:GeometricExpression) -> GeometricExpression {
var exp = [(GeometricNumber, MathOperations)]()
exp.append((lhs, .addition))
exp.append(contentsOf: rhs.expression)
return GeometricExpression(expression: exp)
}
let g1 = 10*e(1)
let g2 = 20*e(2)
let g3 = 3.2*e(3)
g1 + g2 // This works fine
g1 + g2 + g3 // This gives compiler error.
here is the code where > 1 chaining has issues
g1 + g2 // This works fine
g1 + g2 + g3 // This gives compiler error.
here is the error
expression failed to parse:
error: pga.playground:101:9: error: cannot convert value of type 'GeometricExpression' to expected argument type 'GeometricNumber'
g1 + g2 + g3
^
error: pga.playground:101:11: error: cannot convert value of type 'GeometricNumber' to expected argument type 'GeometricExpression'
g1 + g2 + g3
^
I'm not sure if there is any constraint for operator overloading to have lhs and rhs of same type? here "+" operator is just an example I have faced similar issues for many other operators too. Is there any way to hint the compiler to pick the next possible one? Any inputs will be helpful.
@tera could you please run the first code of mine and give some answer. Even I'm expecting it to work, but it is not. I also confirmed in my second post that the code I pasted also works with "TypeA/B/C" Example. question is not just "n+n" or "e + e" the syntax "n + n + e" should work in your case and my case. But for some reason it is not working in my first example code, which is what I'm more interested in.
import Foundation
protocol GeometricTerm {
func add(_ v: GeometricTerm) -> GeometricTerm
}
func + (a: GeometricTerm, b: GeometricTerm) -> GeometricTerm {
a.add(b)
}
struct GeometricNumber: GeometricTerm {
func add(_ v: GeometricNumber) -> GeometricExpression {
fatalError("TODO")
}
func add(_ v: GeometricExpression) -> GeometricExpression {
fatalError("TODO")
}
func add(_ v: GeometricTerm) -> GeometricTerm {
if let v = v as? GeometricNumber {
return add(v)
} else if let v = v as? GeometricExpression {
return add(v)
} else {
fatalError()
}
}
}
struct GeometricExpression: GeometricTerm {
func add(_ v: GeometricNumber) -> GeometricExpression {
fatalError("TODO")
}
func add(_ v: GeometricExpression) -> GeometricExpression {
fatalError("TODO")
}
func add(_ v: GeometricTerm) -> GeometricTerm {
if let v = v as? GeometricNumber {
return add(v)
} else if let v = v as? GeometricExpression {
return add(v)
} else {
fatalError()
}
}
}
func test() {
let n = GeometricNumber()
let e = GeometricExpression()
_ = n + n + n
_ = n + n + e
_ = n + e + n
_ = n + e + e
_ = e + n + n
_ = e + n + e
_ = e + e + n
_ = e + e + e
}
@tera thanks for this solution. I already have something similar in my code. But as you mentioned, it becomes more ugly on the call sight and lot of code to maintain. So I wanted to understand why left associative operators (in my case +) is not chaining along even though required overloads are provided.
BTW, consider using a single enum for your number / expression / etc types - it will be a single type and the issue above will automatically disappear - and no need for a protocol or type casts.
extension GeometricNumber {
init(_ e:e) {
self.e = e
self.coefficient = 1
}
}
var f1 = GeometricNumber(e(1))
var f2 = GeometricNumber(e(2))
var f3 = GeometricNumber(e(3))
GeometricNumber(e(1)) + GeometricNumber(e(2)) + GeometricNumber(e(3)) // this works!
f1 + f2 + f3 // This gives compiler error
Important part is here
GeometricNumber(e(1)) + GeometricNumber(e(2)) + GeometricNumber(e(3)) // this works!
f1 + f2 // This works
f1 + f2 + f3 // This gives compiler error
If we directly create the variable and chain along, multiple operator chaining works. Where as if we assign the same to a variable and try to use operator chaining, it works only for the first chaining.
Without having looked into the rest of the question, it's important to point out that under no circumstances should you attempt to re-declare standard library operators and assign them to custom precedence groups like this.
This will cause all sorts of unexpected behavior, and I don't think there's any testing on the part of the compiler to make sure that it even works even a little bit sensibly.
Hi @xwu thanks for your inputs. Can you please let us know why this is not compiling then? There are no custom precedence group defined here. But still without the additional cast compiler is not able to identify the type.
The + operator is heavily overloaded, and one "cheat" to help speed up type checking of expressions was to teach the type checker to favor same-type operands for known operators defined in the standard library.
Unfortunately this can lead to custom uses of the operator not working as they should; fixing this may negatively impact type checking such that many other valid expressions stop compiling, though. (cc @hborla)
If you modify your example to use a custom operator that isn't defined in the standard library (such as +++, for example), you will not have this problem.
Changing the precedence and associativity of standard library operators should absolutely be an error, in my view. That requires an Evolution proposal and I don't have time to do it at the moment.
@tera This is what @xwu is also mentioning, using a new operator will not have issues.
This approach will again leads to lot of boilerplate code. Instead if you are okay with using "." with operators then you can simply create a new operators for ".+" or ".-" etc as shown below.
infix operator .+ : YourChoiceOfPrecedence
func .+ (_ lhs:TypeA, _ rhs:TypeB) -> TypeC {}
let a = TypeA(a:10)
let b = TypeB(b:10)
let c = TypeC(a:TypeA(a:10),b:TypeB(b:10))
a .+ b .+ c .+ c .+ c // Compiles and works as expected
This is much less code.
Also I prefer free functions, in that way it opens up the composability of these operators with other functions.