pattern matching in if improvement?


(Maury Markowitz) #1

Before I stick my head into the other list, consider:

   if statusCode >= 200 && statusCode <= 299

I'm sure examples of something like this occur throughout your code. But the actual semantics of the test is hidden here, you're really testing if statusCode lies within a range. Swift 2.0 has a couple of options for this, but I find them all rather odd. The most recommended is:

   if case 0...100 = someInteger

This syntax has problems. For one thing, it's written backwards compared to most people's code...

   if someinteger == 100

not...

   if 100 == someinteger

so it just *feels* wrong. In addition, the use of "case" seems odd too. And finally, there's the use of the single equals sign in a test, which goes against everything we've learned in C-like languages.

So unless I'm missing something, can anyone offer a reason this wouldn't work?

  if someinteger in 0...100


(Erica Sadun) #2

Before I stick my head into the other list, consider:

  if statusCode >= 200 && statusCode <= 299

I'm sure examples of something like this occur throughout your code. But the actual semantics of the test is hidden here, you're really testing if statusCode lies within a range. Swift 2.0 has a couple of options for this, but I find them all rather odd. The most recommended is:

  if case 0...100 = someInteger

While I prefer:

if 200...299 ~= statusCode { print("within 200-299") }

I see that you're asking specifically about case/=. But keep that example in mind because I'm
going to circle back to it.

This syntax has problems. For one thing, it's written backwards compared to most people's code...

  if someinteger == 100

not...

  if 100 == someinteger

so it just *feels* wrong. In addition, the use of "case" seems odd too. And finally, there's the use of the single equals sign in a test, which goes against everything we've learned in C-like languages.

So unless I'm missing something, can anyone offer a reason this wouldn't work?

if someinteger in 0...100

This is a built-in problem with "if case/=". Starting with the core statement :

if case pattern = value {...}

It's far easier to read and understand the equivalent switch than the one-liner:

switch (value) {
case pattern: ...
default: break
}

Once you convert to a switch, the "If this value can be matched to this pattern"
becomes a lot less mentally weird but there's also a lot of extra fluff that if case
attempts to trim away. Here's a concrete example. "In the case that the pattern
Test.A(Int) can be matched to this value then bind x to the associated Int value"

let value = Test.A(23)

if case Test.A(let x) = value {
    print(x) // will print 23
}

Again the switch is a lot more intuitive to read, but contains a lot of unneeded
details that can and should be trimmable:

switch (value) {
case Test.A(let x): ...
default: break
}

And here's the oddest example of this Case/= construct I can think of in terms
of the "read through" not matching the actual programming intent of "In the
case that the array indices can be matched to this value"

if case array.indices = array.startIndex { print("strange but yup") }

And its switch equivalent, which is way more obvious in terms of intent:

switch (array.startIndex) {
case array.indices: ...
default: break
}

Now back to your original point. Could this be expressed better? For sure. I think these are far more readable:

if value in range {...} // vs if range ~=
if value matches pattern {...} // vs if case pattern = value

And for these specific examples, they'd look like this in an updated Swift that adopted these changes:

if statusCode in 200...299 { print("within 200-299") }
if value matches Test.A(let x) { print(x) } // will print 23
if array.startIndex in array.indices { print("the ~= variation") }
if array.startIndex matches array.indices { print ("better example Case/=") }

That said, I've also made my opinion clear over there that the use of "let" and "var"
in "if let" unnecessarily overloads constant and variable binding (it's testing something
that actually acts differently than the standalone let due to unwrapping). This got nowhere
for a variety of compelling and less compelling reasons. (I'd prefer "if bind" even if it
sacrifices a variable variant.)

I certainly think it's worth doing at least a [Pitch] over in -evolution with the alternate
constructs.

-- E

···

On Mar 26, 2016, at 3:47 PM, Maury Markowitz via swift-users <swift-users@swift.org> wrote:


(Neil Faiman) #3

How about

  if (200..<299).contains(statusCode)

···

On Mar 26, 2016, at 5:47 PM, Maury Markowitz via swift-users <swift-users@swift.org> wrote:

Before I stick my head into the other list, consider:

  if statusCode >= 200 && statusCode <= 299

I'm sure examples of something like this occur throughout your code. But the actual semantics of the test is hidden here, you're really testing if statusCode lies within a range. Swift 2.0 has a couple of options for this, but I find them all rather odd. The most recommended is:

  if case 0...100 = someInteger

This syntax has problems. For one thing, it's written backwards compared to most people's code...

  if someinteger == 100


(Lukas Stabe) #4

I'd love to see `if value matches pattern`. Every time I use `if case` I have to stop and think to remember the weird syntax. I get where it came from, but I think `matches` better describes what happens here (one is immediately reminded of pattern matching).

I don't know wether this might be unfeasible to do due to compiler performance limitations, but I would support a proposal to replace `if case` with `if value matches pattern`.

An alternative may be to introduce a custom operator that just calls `~=` with the arguments reversed, but imho this should rather be fixed at the language level.

– Lukas

···

On 26 Mar 2016, at 18:13, Erica Sadun via swift-users <swift-users@swift.org> wrote:

On Mar 26, 2016, at 3:47 PM, Maury Markowitz via swift-users <swift-users@swift.org> wrote:

Before I stick my head into the other list, consider:

  if statusCode >= 200 && statusCode <= 299

I'm sure examples of something like this occur throughout your code. But the actual semantics of the test is hidden here, you're really testing if statusCode lies within a range. Swift 2.0 has a couple of options for this, but I find them all rather odd. The most recommended is:

  if case 0...100 = someInteger

While I prefer:

if 200...299 ~= statusCode { print("within 200-299") }

I see that you're asking specifically about case/=. But keep that example in mind because I'm
going to circle back to it.

This syntax has problems. For one thing, it's written backwards compared to most people's code...

  if someinteger == 100

not...

  if 100 == someinteger

so it just *feels* wrong. In addition, the use of "case" seems odd too. And finally, there's the use of the single equals sign in a test, which goes against everything we've learned in C-like languages.

So unless I'm missing something, can anyone offer a reason this wouldn't work?

if someinteger in 0...100

This is a built-in problem with "if case/=". Starting with the core statement :

if case pattern = value {...}

It's far easier to read and understand the equivalent switch than the one-liner:

switch (value) {
case pattern: ...
default: break
}

Once you convert to a switch, the "If this value can be matched to this pattern"
becomes a lot less mentally weird but there's also a lot of extra fluff that if case
attempts to trim away. Here's a concrete example. "In the case that the pattern
Test.A(Int) can be matched to this value then bind x to the associated Int value"

let value = Test.A(23)

if case Test.A(let x) = value {
    print(x) // will print 23
}

Again the switch is a lot more intuitive to read, but contains a lot of unneeded
details that can and should be trimmable:

switch (value) {
case Test.A(let x): ...
default: break
}

And here's the oddest example of this Case/= construct I can think of in terms
of the "read through" not matching the actual programming intent of "In the
case that the array indices can be matched to this value"

if case array.indices = array.startIndex { print("strange but yup") }

And its switch equivalent, which is way more obvious in terms of intent:

switch (array.startIndex) {
case array.indices: ...
default: break
}

Now back to your original point. Could this be expressed better? For sure. I think these are far more readable:

if value in range {...} // vs if range ~=
if value matches pattern {...} // vs if case pattern = value

And for these specific examples, they'd look like this in an updated Swift that adopted these changes:

if statusCode in 200...299 { print("within 200-299") }
if value matches Test.A(let x) { print(x) } // will print 23
if array.startIndex in array.indices { print("the ~= variation") }
if array.startIndex matches array.indices { print ("better example Case/=") }

That said, I've also made my opinion clear over there that the use of "let" and "var"
in "if let" unnecessarily overloads constant and variable binding (it's testing something
that actually acts differently than the standalone let due to unwrapping). This got nowhere
for a variety of compelling and less compelling reasons. (I'd prefer "if bind" even if it
sacrifices a variable variant.)

I certainly think it's worth doing at least a [Pitch] over in -evolution with the alternate
constructs.

-- E
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Wallacy) #5

var someInteger = 250
if 200..<299 ~= someInteger {
  print("works")
}

···

Em sáb, 26 de mar de 2016 18:47, Maury Markowitz via swift-users < swift-users@swift.org> escreveu:

Before I stick my head into the other list, consider:

   if statusCode >= 200 && statusCode <= 299

I'm sure examples of something like this occur throughout your code. But
the actual semantics of the test is hidden here, you're really testing if
statusCode lies within a range. Swift 2.0 has a couple of options for this,
but I find them all rather odd. The most recommended is:

   if case 0...100 = someInteger

This syntax has problems. For one thing, it's written backwards compared
to most people's code...

   if someinteger == 100

not...

   if 100 == someinteger

so it just *feels* wrong. In addition, the use of "case" seems odd too.
And finally, there's the use of the single equals sign in a test, which
goes against everything we've learned in C-like languages.

So unless I'm missing something, can anyone offer a reason this wouldn't
work?

  if someinteger in 0...100

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Maury Markowitz) #6

Yes, I should have touched on this one too.

My concern here is one of the concerns of the case version, it's cart before the horse. General programming practice for tests is something like:

(the thing you want to test) (the test) (what you test against)

This syntax reverses that ordering. Sure, this is a purely stylistic concern, but so much in programming is.

As someone else noted, we could simply allow this to be reversed, but that raises the question of why bother?

We already have the "in" operator. Everyone knows it, because you have to, especially under Swift 3. Using it in the if statement works exactly the same way it does in the for. And it requires nothing new to learn, unlike ~= which is an edge use-case.

···

On Mar 26, 2016, at 7:13 PM, Erica Sadun <erica@ericasadun.com> wrote:

if 200...299 ~= statusCode { print("within 200-299") }


(TJ Usiyan) #7

I wrote these a while ago and considered proposing that they be added.
Might as well throw them into this thread since they are another approach
to solving the problem.

import Foundation
    // MARK: <
    public func <<T : Comparable>(lhs: T, rhs: T) -> (result: Bool,
value:T) {
        return (lhs < rhs, rhs)
    }

    public func <<T : Comparable>(lhs: (result: Bool, value: T), rhs: T) ->
Bool {
            return lhs.result && (lhs.value < rhs)
        }

    public func <<T : Comparable>(lhs: (Bool, T), rhs: T) -> (Bool, T) {
            return (lhs < rhs, rhs)
        }

// MARK: >
public func ><T : Comparable>(lhs: T, rhs: T) -> (result: Bool, value:T) {
    return (lhs > rhs, rhs)
    }

public func ><T : Comparable>(lhs: (result: Bool, value: T), rhs: T) ->
Bool {
    return lhs.result && (lhs.value > rhs)
    }

public func ><T : Comparable>(lhs: (Bool, T), rhs: T) -> (Bool, T) {
    return (lhs > rhs, rhs)
    }
// MARK: >=
public func >=<T : Comparable>(lhs: T, rhs: T) -> (result: Bool, value:T) {
    return (lhs >= rhs, rhs)
    }

public func >=<T : Comparable>(lhs: (result: Bool, value: T), rhs: T) ->
Bool {
    return lhs.result && (lhs.value >= rhs)
    }

public func >=<T : Comparable>(lhs: (Bool, T), rhs: T) -> (Bool, T) {
    return (lhs >= rhs, rhs)
    }

// MARK: <=
public func <=<T : Comparable>(lhs: T, rhs: T) -> (result: Bool, value:T) {
    return (lhs <= rhs, rhs)
    }

public func <=<T : Comparable>(lhs: (result: Bool, value: T), rhs: T) ->
Bool {
    return lhs.result && (lhs.value <= rhs)
    }

public func <=<T : Comparable>(lhs: (Bool, T), rhs: T) -> (Bool, T) {
    return (lhs <= rhs, rhs)
    }

// MARK: ==
public func ==<T : Comparable>(lhs: T, rhs: T) -> (result: Bool, value:T) {
    return (lhs == rhs, rhs)
    }

public func ==<T : Comparable>(lhs: (result: Bool, value: T), rhs: T) ->
Bool {
    return lhs.result && (lhs.value == rhs)
    }

public func ==<T : Comparable>(lhs: (Bool, T), rhs: T) -> (Bool, T) {
    return (lhs == rhs, rhs)
    }
let value = 5

if (0 < value) < 10 {
    print("in the middle!")
    } else {
    print("nope")
    }

I'd love to see `if value matches pattern`. Every time I use `if case` I

have to stop and think to remember the weird syntax. I get where it came
from, but I think `matches` better describes what happens here (one is
immediately reminded of pattern matching).

I don't know wether this might be unfeasible to do due to compiler

performance limitations, but I would support a proposal to replace `if
case` with `if value matches pattern`.

An alternative may be to introduce a custom operator that just calls `~=`

with the arguments reversed, but imho this should rather be fixed at the
language level.

– Lukas

Before I stick my head into the other list, consider:

  if statusCode >= 200 && statusCode <= 299

I'm sure examples of something like this occur throughout your code. But

the actual semantics of the test is hidden here, you're really testing if
statusCode lies within a range. Swift 2.0 has a couple of options for this,
but I find them all rather odd. The most recommended is:

  if case 0...100 = someInteger

While I prefer:

if 200...299 ~= statusCode { print("within 200-299") }

I see that you're asking specifically about case/=. But keep that example

in mind because I'm

going to circle back to it.

This syntax has problems. For one thing, it's written backwards compared

to most people's code...

  if someinteger == 100

not...

  if 100 == someinteger

so it just *feels* wrong. In addition, the use of "case" seems odd too.

And finally, there's the use of the single equals sign in a test, which
goes against everything we've learned in C-like languages.

So unless I'm missing something, can anyone offer a reason this wouldn't

work?

if someinteger in 0...100

This is a built-in problem with "if case/=". Starting with the core

statement :

if case pattern = value {...}

It's far easier to read and understand the equivalent switch than the

one-liner:

switch (value) {
case pattern: ...
default: break
}

Once you convert to a switch, the "If this value can be matched to this

pattern"

becomes a lot less mentally weird but there's also a lot of extra fluff

that if case

attempts to trim away. Here's a concrete example. "In the case that the

pattern

Test.A(Int) can be matched to this value then bind x to the associated

Int value"

let value = Test.A(23)

if case Test.A(let x) = value {
    print(x) // will print 23
}

Again the switch is a lot more intuitive to read, but contains a lot of

unneeded

details that can and should be trimmable:

switch (value) {
case Test.A(let x): ...
default: break
}

And here's the oddest example of this Case/= construct I can think of in

terms

of the "read through" not matching the actual programming intent of "In

the

case that the array indices can be matched to this value"

if case array.indices = array.startIndex { print("strange but yup") }

And its switch equivalent, which is way more obvious in terms of intent:

switch (array.startIndex) {
case array.indices: ...
default: break
}

Now back to your original point. Could this be expressed better? For

sure. I think these are far more readable:

if value in range {...} // vs if range ~=
if value matches pattern {...} // vs if case pattern = value

And for these specific examples, they'd look like this in an updated

Swift that adopted these changes:

if statusCode in 200...299 { print("within 200-299") }
if value matches Test.A(let x) { print(x) } // will print 23
if array.startIndex in array.indices { print("the ~= variation") }
if array.startIndex matches array.indices { print ("better example

Case/=") }

That said, I've also made my opinion clear over there that the use of

"let" and "var"

in "if let" unnecessarily overloads constant and variable binding (it's

testing something

that actually acts differently than the standalone let due to

unwrapping). This got nowhere

for a variety of compelling and less compelling reasons. (I'd prefer "if

bind" even if it

sacrifices a variable variant.)

I certainly think it's worth doing at least a [Pitch] over in -evolution

with the alternate

···

On Sun, Mar 27, 2016 at 5:19 AM, Lukas Stabe via swift-evolution < swift-evolution@swift.org> wrote:

On 26 Mar 2016, at 18:13, Erica Sadun via swift-users < swift-users@swift.org> wrote:
On Mar 26, 2016, at 3:47 PM, Maury Markowitz via swift-users < swift-users@swift.org> wrote:
constructs.

-- E

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Sweeris) #8

I'm not where I can check, but is "=~" taken?

···

Sent from my iPhone

On Mar 26, 2016, at 22:19, Lukas Stabe via swift-users <swift-users@swift.org> wrote:

An alternative may be to introduce a custom operator that just calls `~=` with the arguments reversed, but imho this should rather be fixed at the language level.

– Lukas