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.