Breaking this into a different thread…
One suggestion which I haven’t seen (maybe it is too crazy) is the idea of introducing shadowing of operators within a scope (similar to variables).
Now this would need a TON of design work to make it actually work, but the gist would be:
if blah == foo {
//We can override/shadow the ‘<‘ operator within this scope as long as the signature is the same
let (<) = String.comparisonOperation(case: .insensitive, locale: .current) //Here we grab a closure produced by a static function on String
if a < b { //Within this scope < will be case insensitive
//Stuff
}
}
//Outside of the scope, < is back to it’s lovable self
Thoughts on the general idea?
Thanks,
Jon
···
I know you want to defer this for now, so feel free to set this part of the email aside, but here's a quick list of solutions I've ballparked:
1. Your "one operand carries the options" solution.
2. As I mentioned, do something that effectively overloads comparison operators to return them in a symbolic form. You're right about the ambiguity problem, though.
3. Like #2, but with slightly modified operators, e.g.:
if localized(fu &< br, case: .insensitive) { … }
4. Reintroduce something like the old `BooleanType` and have *all* comparisons construct a symbolic form that can be coerced to boolean. This is crazy, but actually probably useful in other places; I once experimented with constructing NSPredicates like this.
protocol BooleanProtocol { var boolValue: Bool { get } }
struct Comparison<Operand: Comparable> {
var negated: Bool
var sortOrder: SortOrder
var left: Operand
var right: Operand
func evaluate(_ actualSortOrder: SortOrder) -> Bool {
// There's circularity problems here, because `==` would itself return a `Comparison`,
// but I think you get the idea.
return (actualSortOrder == sortOrder) != negated
}
}
extension Comparison: BooleanProtocol {
var boolValue: Bool {
return evaluate(left.compared(to: right))
}
}
func < <ComparableType: Comparable>(lhs: ComparableType, rhs: ComparableType) -> Comparison {
return Comparison(negated: false, sortOrder: .before, left: lhs, right: rhs)
}
func <= <ComparableType: Comparable>(lhs: ComparableType, rhs: ComparableType) -> Comparison {
return Comparison(negated: true, sortOrder: .after, left: lhs, right: rhs)
}
// etc.
// Now for our special String comparison thing:
func localized(_ expr: Comparison<String>, case: StringCaseSensitivity? = nil, …) -> Bool {
return expr.evaluate(expr.left.compare(expr.right, case: case, …))
}5. Actually add some all-new piece of syntax that allows you to add options to an operator. Bad part is that this is ugly and kind of weird; good part is that this could probably be used in other places as well. Strawman example:
// Use:
if fu < br %(case: .insensitive, locale: .current) { … }
// Definition:
func < (lhs: String, rhs: String, case: StringCaseSensitivity? = nil, …) -> Bool { … }6. Punt on this until we have macros. Once we do, have the function be a macro which alters the comparisons passed to it. Bad part is that this doesn't give us a solution for at least a version or two.