Strings in Swift 4

From: Dave Abrahams <dabrahams@apple.com>
To: Brent Royal-Gordon <brent@architechies.com>

While it's great that `compared(to:case:etc.)` is parallel to `compared(to:)`, you don't actually want to *use* anything like `compared(to:)` if you can help it. Think about the clarity at the use site:

  if foo.compared(to: bar, case: .insensitive, locale: .current) == .before { … }

Right. We intend to keep the usual comparison operators.

Poor readability of "foo <=> bar == .before" is another reason we think that giving up on "<=>" is no great loss.

The operands and sense of the comparison are kind of lost in all this garbage. You really want to see `foo < bar` in this code somewhere, but you don't.

Yeah, we thought about trying to build a DSL for that, but failed. I think the best possible option would be something like:

foo.comparison(case: .insensitive, locale: .current) < bar

The biggest problem is that you can build things like

   fu = foo.comparison(case: .insensitive, locale: .current)
   br = bar.comparison(case: .sensitive)
   fu < br // what does this mean?

We could even prevent such nonsense from compiling, but the cost in library API surface area is quite large.

I'm struggling a little with the naming and syntax, but as a general approach, I think we want people to use something more like this:

  if StringOptions(case: .insensitive, locale: .current).compare(foo < bar) { … }

Yeah, we can't do that without making

  let a = foo < bar

ambiguous

So, cue crazy idea #1, but what about something like this?

struct StringComparison {

  let leftHand: String
  let rightHand: String
  let comparison: SortOrder

  var insensitive: Bool {
    ...
  }
  // … etc
}

func <(lhs: String, rhs: String) -> StringComparison { // similar for ==, >, etc.
  return StringComparison(leftHand: lhs, rightHand: rhs, comparison: .before)
}

Then:

  if (a < b).insensitive {
    ...
  }

This would fix the ambiguity of:

  let a = foo < bar // ‘a' is StringComparison
  if a.insensitive {

  }

IMHO, the big problem with this is that the most obvious default case really should fall-through to a boolean value for the comparison struct:

  if a < b { // case-sensitive, non-localized compare
    ...
  }

...but I can’t figure out how to make that work without the old BooleanType protocol, maybe someone smarter than I could…

—Karim

From: Dave Abrahams <dabrahams@apple.com>
To: Brent Royal-Gordon <brent@architechies.com>

While it's great that `compared(to:case:etc.)` is parallel to `compared(to:)`, you don't actually want to *use* anything like `compared(to:)` if you can help it. Think about the clarity at the use site:

  if foo.compared(to: bar, case: .insensitive, locale: .current) == .before { … }

Right. We intend to keep the usual comparison operators.

Poor readability of "foo <=> bar == .before" is another reason we think that giving up on "<=>" is no great loss.

The operands and sense of the comparison are kind of lost in all this garbage. You really want to see `foo < bar` in this code somewhere, but you don't.

Yeah, we thought about trying to build a DSL for that, but failed. I think the best possible option would be something like:

foo.comparison(case: .insensitive, locale: .current) < bar

The biggest problem is that you can build things like

   fu = foo.comparison(case: .insensitive, locale: .current)
   br = bar.comparison(case: .sensitive)
   fu < br // what does this mean?

We could even prevent such nonsense from compiling, but the cost in library API surface area is quite large.

I'm struggling a little with the naming and syntax, but as a general approach, I think we want people to use something more like this:

  if StringOptions(case: .insensitive, locale: .current).compare(foo < bar) { … }

Yeah, we can't do that without making

  let a = foo < bar

ambiguous

So, cue crazy idea #1, but what about something like this?

struct StringComparison {

  let leftHand: String
  let rightHand: String
  let comparison: SortOrder

  var insensitive: Bool {
    ...
  }
  // … etc
}

func <(lhs: String, rhs: String) -> StringComparison { // similar for ==, >, etc.
  return StringComparison(leftHand: lhs, rightHand: rhs, comparison: .before)
}

Then:

  if (a < b).insensitive {
    ...
  }

This would fix the ambiguity of:

  let a = foo < bar // ‘a' is StringComparison
  if a.insensitive {

  }

IMHO, the big problem with this is that the most obvious default case really should fall-through to a boolean value for the comparison struct:

  if a < b { // case-sensitive, non-localized compare
    ...
  }

...but I can’t figure out how to make that work without the old BooleanType protocol, maybe someone smarter than I could…

Yeah... IMO this is the language telling us "don't do that." for my part, I'm not really in the market for ideas about how to do this that cut against the grain of the language. I'd settle for a slightly less expressive API if it means avoiding surprising corner case behaviors. And there's so much more to discuss here than how to clean up this syntax. If someone comes up with an expressive notation that's free of issues, we can always implement it as an add-on later.

-Dave

···

Sent from my iPad

On Jan 22, 2017, at 11:25 AM, Karim Nassar <karim@karimnassar.com> wrote: