Allow let binding of non-optionals


(Russ Bishop) #1

I often want to bind variables to a specific scope, or perhaps I have three optionals I want to bind and one non-optional. (Often I need that non-optional value as part of a where condition check but not being able to bind it means I leak the variable to the outer scope).

Not being able to bind non-optionals breaks the flow of the code and forces me to make a minor context switch when it really doesn’t matter, nor does it aid code clarity.

Does anyone have strong thoughts about this? I tried searching the evolution repo and the mailing list and didn’t see anything.

—russ


(Krzysztof Siejkowski) #2

FWIW, not sure if it fits your desired outcome, but some time ago I’ve proposed the idea of `CustomOptionalConvertible` protocol:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/000918.html

This could be used to extend the let binding syntax to any arbitrary type, as long as the user provides the valid translation to Optional<Type>.

The proposal, unfortunately, did not get a lot of attention at the time.

The main drawback to such a solution for your case is that CustomOptionalConvertible is an opt-in mechanism, so you’d need to write the implementation of CustomOptionalConvertible to each type you wish to let bind:

extension Type : CustomOptionalConvertible {
  typealias Wrapped = Type
  public var optional: Optional<Type> { get {
    return .Some(self)
  }}
}

Would the necessity for the above boilerplate be a show-stopper to you?
If so, would you rather see the extension of let bind syntax as default on for all the types?

Cheers,
Krzysztof

···

On 7 January 2016 at 07:41:06, Russ Bishop via swift-evolution (swift-evolution@swift.org) wrote:

I often want to bind variables to a specific scope, or perhaps I have three optionals I want to bind and one non-optional. (Often I need that non-optional value as part of a where condition check but not being able to bind it means I leak the variable to the outer scope).

Not being able to bind non-optionals breaks the flow of the code and forces me to make a minor context switch when it really doesn’t matter, nor does it aid code clarity.

Does anyone have strong thoughts about this? I tried searching the evolution repo and the mailing list and didn’t see anything.

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


(H2CO3) #3

+1 to that. It's really useful; I think optional binding is a special case of the more general notion of "bind and check as Boolean", which is a pattern that comes up everywhere. It's really more convenient than having to declare another variable in the outer scope (cluttering it).

···

Sent from my iPad

On 2016. jan. 7., at 7:40, Russ Bishop <xenadu@gmail.com> wrote:

I often want to bind variables to a specific scope, or perhaps I have three optionals I want to bind and one non-optional. (Often I need that non-optional value as part of a where condition check but not being able to bind it means I leak the variable to the outer scope).

Not being able to bind non-optionals breaks the flow of the code and forces me to make a minor context switch when it really doesn’t matter, nor does it aid code clarity.

Does anyone have strong thoughts about this? I tried searching the evolution repo and the mailing list and didn’t see anything.

—russ


#4

+1.

`if let` does two things: 1. unwrap the optional, 2. define a new variable, scoped to the `if` statement.

Sometimes we don’t need the unwrapping, but we’d like the new, temporary, scoped variable.

Gwendal

···

Le 7 janv. 2016 à 07:40, Russ Bishop via swift-evolution <swift-evolution@swift.org> a écrit :

I often want to bind variables to a specific scope, or perhaps I have three optionals I want to bind and one non-optional. (Often I need that non-optional value as part of a where condition check but not being able to bind it means I leak the variable to the outer scope).

Not being able to bind non-optionals breaks the flow of the code and forces me to make a minor context switch when it really doesn’t matter, nor does it aid code clarity.

Does anyone have strong thoughts about this? I tried searching the evolution repo and the mailing list and didn’t see anything.

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


(Jacob Bandes-Storch) #5

+1. I've had a hard time cleaning up certain pieces of code that involve
multiple optional chains and condition checks.

One solution I came up with, but have never used in practice (because it's
too obscure), is this:

    extension Equatable {
        func *except*(excludedValue: Self) -> Self? {
            return self == excludedValue ? nil : self
        }
    }

Then you can make a non-optional Equatable value into an Optional by
checking against a known excluded value:

    if let input = inputs.first, ...,
        let matchLoc = regex.rangeOfFirstMatchInString(...).location.
*except*(NSNotFound)
    {
        // Do something with matchLoc
    } else ...

Another, more flexible, option is to make a new protocol with an extension
that provides similar functionality, but with a closure argument. Then you
unfortunately have to add a trivial conformance for any type you need to
use.

    protocol Satisfying {}
    extension Satisfying {
        func *satisfying*(@noescape predicate: Self -> Bool) -> Self? {
            return predicate(self) ? self : nil
        }
    }

    extension NSRange: Satisfying {}

    if let input = inputs.first, ...,
        let matchRange = regex.rangeOfFirstMatchInString(...).*satisfying*({
$0.location != NSNotFound })
    {
        // Do something with matchRange
    } else ...

To be clear, here's what the user *wants* to do; the clearest code:

    if let input = inputs.first, ...,
        let matchRange = regex.rangeOfFirstMatchInString(...)
        *where* matchRange.location != NSNotFound
    {
        // Do something with matchRange
    } else ...

But that's not allowed, because rangeOfFirstMatchInString returns a
non-optional NSRange.

(Arguably this is a bug to be fixed in the regex API, which should be
replacing NSRange with Range<Int>?, but it's a good example of something
that crops up in plenty of other places.)

Jacob

···

On Wed, Jan 6, 2016 at 11:06 PM, Krzysztof Siejkowski via swift-evolution < swift-evolution@swift.org> wrote:

FWIW, not sure if it fits your desired outcome, but some time ago I’ve
proposed the idea of `CustomOptionalConvertible` protocol:

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/000918.html

This could be used to extend the let binding syntax to any arbitrary type,
as long as the user provides the valid translation to Optional<Type>.

The proposal, unfortunately, did not get a lot of attention at the time.

The main drawback to such a solution for your case is that
CustomOptionalConvertible is an opt-in mechanism, so you’d need to write
the implementation of CustomOptionalConvertible to each type you wish to
let bind:

extension Type : CustomOptionalConvertible {
typealias Wrapped = Type
public var optional: Optional<Type> { get {
return .Some(self)
}}
}

Would the necessity for the above boilerplate be a show-stopper to you?
If so, would you rather see the extension of let bind syntax as default on
for all the types?

Cheers,
Krzysztof

On 7 January 2016 at 07:41:06, Russ Bishop via swift-evolution ( > swift-evolution@swift.org) wrote:

I often want to bind variables to a specific scope, or perhaps I have
three optionals I want to bind and one non-optional. (Often I need that
non-optional value as part of a where condition check but not being able to
bind it means I leak the variable to the outer scope).

Not being able to bind non-optionals breaks the flow of the code and
forces me to make a minor context switch when it really doesn’t matter, nor
does it aid code clarity.

Does anyone have strong thoughts about this? I tried searching the
evolution repo and the mailing list and didn’t see anything.

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

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


(Jacob Bandes-Storch) #6

Well, I don't think it should be universally allowed.

Consider:

    if let value = getValue() {
        // do something with value
    }

If getValue() returns an optional, this is fine. But if it's non-optional,
then you've introduced an "if" when there really is *no* conditional
control flow happening. This should still be a compiler error.

It gets trickier when you mix optional and non-optional values:

    if let value1 = somethingOptional(),
        let value2 = somethingNonOptional()
    {
        // should this be allowed?
    }

    if let value1 = somethingNonOptional(),
        let value2 = somethingOptional()
    {
        // How about this?
    }

Here's an alternative idea: allow non-optional bindings in "do" blocks:

    *do let* value = somethingNonOptional() {
        // use value
    } // value is out of scope now

And perhaps you could combine them with if-statements:

    if let value1 = somethingOptional(),
        *do let* value2 = somethingNonOptional()
        where value2 < value1
    {
        // use the values
    }

Jacob Bandes-Storch

···

On Thu, Jan 7, 2016 at 10:41 AM, Gwendal Roué <swift-evolution@swift.org> wrote:

+1.

`if let` does two things: 1. unwrap the optional, 2. define a new
variable, scoped to the `if` statement.

Sometimes we don’t need the unwrapping, but we’d like the new, temporary,
scoped variable.

Gwendal

> Le 7 janv. 2016 à 07:40, Russ Bishop via swift-evolution < > swift-evolution@swift.org> a écrit :
>
> I often want to bind variables to a specific scope, or perhaps I have
three optionals I want to bind and one non-optional. (Often I need that
non-optional value as part of a where condition check but not being able to
bind it means I leak the variable to the outer scope).
>
> Not being able to bind non-optionals breaks the flow of the code and
forces me to make a minor context switch when it really doesn’t matter, nor
does it aid code clarity.
>
>
> Does anyone have strong thoughts about this? I tried searching the
evolution repo and the mailing list and didn’t see anything.
>
>
> —russ
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Joe Groff) #7

'case let' already works like this, by declaring a pattern 'let a' that unconditionally matches anything.

-Joe

···

On Jan 7, 2016, at 10:59 AM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:

        do let value2 = somethingNonOptional()


(Jacob Bandes-Storch) #8

You learn something new every day on this list!

This effectively solves the problems I've had. However, I think the
resulting code is misleading, because "if case let" or just "case let" look
like conditional expressions, when really they aren't.

    if case let x = somethingNonOptional() {
        // use x, *unconditionally*
    } // x out of scope now

I think "do let" would improve readability considerably...

···

On Thu, Jan 7, 2016 at 11:02 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 7, 2016, at 10:59 AM, Jacob Bandes-Storch via swift-evolution < > swift-evolution@swift.org> wrote:

        *do let* value2 = somethingNonOptional()

'case let' already works like this, by declaring a pattern 'let a' that
unconditionally matches anything.

-Joe


#9

That would be fine for me, as long as Xcode suggests adding the missing `do`, or replacing `if` with `do` when I forget one!

Gwendal

···

Le 7 janv. 2016 à 19:59, Jacob Bandes-Storch <jtbandes@gmail.com> a écrit :

Well, I don't think it should be universally allowed.

Consider:

    if let value = getValue() {
        // do something with value
    }

If getValue() returns an optional, this is fine. But if it's non-optional, then you've introduced an "if" when there really is no conditional control flow happening. This should still be a compiler error.

It gets trickier when you mix optional and non-optional values:

    if let value1 = somethingOptional(),
        let value2 = somethingNonOptional()
    {
        // should this be allowed?
    }

    if let value1 = somethingNonOptional(),
        let value2 = somethingOptional()
    {
        // How about this?
    }

Here's an alternative idea: allow non-optional bindings in "do" blocks:

    do let value = somethingNonOptional() {
        // use value
    } // value is out of scope now

And perhaps you could combine them with if-statements:

    if let value1 = somethingOptional(),
        do let value2 = somethingNonOptional()
        where value2 < value1
    {
        // use the values
    }

Jacob Bandes-Storch

On Thu, Jan 7, 2016 at 10:41 AM, Gwendal Roué <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1.

`if let` does two things: 1. unwrap the optional, 2. define a new variable, scoped to the `if` statement.

Sometimes we don’t need the unwrapping, but we’d like the new, temporary, scoped variable.

Gwendal

> Le 7 janv. 2016 à 07:40, Russ Bishop via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :
>
> I often want to bind variables to a specific scope, or perhaps I have three optionals I want to bind and one non-optional. (Often I need that non-optional value as part of a where condition check but not being able to bind it means I leak the variable to the outer scope).
>
> Not being able to bind non-optionals breaks the flow of the code and forces me to make a minor context switch when it really doesn’t matter, nor does it aid code clarity.
>
>
> Does anyone have strong thoughts about this? I tried searching the evolution repo and the mailing list and didn’t see anything.
>
>
> —russ
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


#10

Well, the compiler complains, and does not provide any nice suggestion.

I, and most code I’ve seen so far (including StackOverflow), solve this issue with an extra variable declared in the outer scope:

  // error
  if let x = value...
  
  // OK
  let x = value
  if …

Remember the double role of `if let`: 1. unwrap the optional, 2. define a new variable, scoped to the `if` statement. We have 1, but not 2, and this is the gist of this thread.

Gwendal

···

Le 7 janv. 2016 à 20:02, Joe Groff <jgroff@apple.com> a écrit :

On Jan 7, 2016, at 10:59 AM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

        do let value2 = somethingNonOptional()

'case let' already works like this, by declaring a pattern 'let a' that unconditionally matches anything.

-Joe


(James Campbell) #11

I think in the second case the compiler could give you a warning.

···

Sent from my iPhone

On 7 Jan 2016, at 18:59, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:

Well, I don't think it should be universally allowed.

Consider:

    if let value = getValue() {
        // do something with value
    }

If getValue() returns an optional, this is fine. But if it's non-optional, then you've introduced an "if" when there really is no conditional control flow happening. This should still be a compiler error.

It gets trickier when you mix optional and non-optional values:

    if let value1 = somethingOptional(),
        let value2 = somethingNonOptional()
    {
        // should this be allowed?
    }

    if let value1 = somethingNonOptional(),
        let value2 = somethingOptional()
    {
        // How about this?
    }

Here's an alternative idea: allow non-optional bindings in "do" blocks:

    do let value = somethingNonOptional() {
        // use value
    } // value is out of scope now

And perhaps you could combine them with if-statements:

    if let value1 = somethingOptional(),
        do let value2 = somethingNonOptional()
        where value2 < value1
    {
        // use the values
    }

Jacob Bandes-Storch

On Thu, Jan 7, 2016 at 10:41 AM, Gwendal Roué <swift-evolution@swift.org> wrote:
+1.

`if let` does two things: 1. unwrap the optional, 2. define a new variable, scoped to the `if` statement.

Sometimes we don’t need the unwrapping, but we’d like the new, temporary, scoped variable.

Gwendal

> Le 7 janv. 2016 à 07:40, Russ Bishop via swift-evolution <swift-evolution@swift.org> a écrit :
>
> I often want to bind variables to a specific scope, or perhaps I have three optionals I want to bind and one non-optional. (Often I need that non-optional value as part of a where condition check but not being able to bind it means I leak the variable to the outer scope).
>
> Not being able to bind non-optionals breaks the flow of the code and forces me to make a minor context switch when it really doesn’t matter, nor does it aid code clarity.
>
>
> Does anyone have strong thoughts about this? I tried searching the evolution repo and the mailing list and didn’t see anything.
>
>
> —russ
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

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


(Guillaume Lessard) #12

This is an interesting workaround! However, outside of switch statements I don’t think I’ve used a “case let” that made for clear re-reading. Maybe I’m hobbled by switches.

The workaround I’ve used for Russ’s situation is to build a temporary Optional; `case let` hardly helps:

if let x = x, y = Optional(calculate(x, z)) where condition(x, y) { print(x, y) }

if let x = x, case let y = calculate(x, z) where condition(x, y) { print(x, y) }

In any case, I agree with Russ that this variable binding behaviour is an occasional irritant.

Guillaume Lessard

···

On 7 janv. 2016, at 12:02, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

'case let' already works like this, by declaring a pattern 'let a' that unconditionally matches anything.


(Jacob Bandes-Storch) #13

I think you might have misunderstood Joe's suggestion — it does work for
me. Here's an example:

    if let y = somethingOptional(),
        case let x = somethingNonOptional()
        where x != y
    {
        // Rejoice!
    }

Jacob Bandes-Storch

···

On Thu, Jan 7, 2016 at 11:07 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 7 janv. 2016 à 20:02, Joe Groff <jgroff@apple.com> a écrit :

On Jan 7, 2016, at 10:59 AM, Jacob Bandes-Storch via swift-evolution < > swift-evolution@swift.org> wrote:

        *do let* value2 = somethingNonOptional()

'case let' already works like this, by declaring a pattern 'let a' that
unconditionally matches anything.

-Joe

Well, the compiler complains, and does not provide any nice suggestion.

I, and most code I’ve seen so far (including StackOverflow), solve this
issue with an extra variable declared in the outer scope:

// error
if let x = value...
// OK
let x = value
if …

Remember the double role of `if let`: 1. unwrap the optional, 2. define a
new variable, scoped to the `if` statement. We have 1, but not 2, and this
is the gist of this thread.

Gwendal


(Jacob Bandes-Storch) #14

I see I spoke to soon. Indeed there is a warning "'if' condition is always
true" when you use *only* "if case let x = nonOptional()" with no actual
conditions. That's good, but as you said, leaves us without a solution for
the original problem (in the case where you're not trying to mix it with
optionals).

As much as I think "do let" makes sense:

    do let x = somethingNonOptional() {
        // use x
    }

It's worth pointing out that this works today by just moving the brace
around:

    do { let x = somethingNonOptional()
        // use x
    }

Jacob Bandes-Storch

···

On Thu, Jan 7, 2016 at 11:08 AM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

I think you might have misunderstood Joe's suggestion — it does work for
me. Here's an example:

    if let y = somethingOptional(),
        case let x = somethingNonOptional()
        where x != y
    {
        // Rejoice!
    }

Jacob Bandes-Storch

On Thu, Jan 7, 2016 at 11:07 AM, Gwendal Roué <gwendal.roue@gmail.com> > wrote:

Le 7 janv. 2016 à 20:02, Joe Groff <jgroff@apple.com> a écrit :

On Jan 7, 2016, at 10:59 AM, Jacob Bandes-Storch via swift-evolution < >> swift-evolution@swift.org> wrote:

        *do let* value2 = somethingNonOptional()

'case let' already works like this, by declaring a pattern 'let a' that
unconditionally matches anything.

-Joe

Well, the compiler complains, and does not provide any nice suggestion.

I, and most code I’ve seen so far (including StackOverflow), solve this
issue with an extra variable declared in the outer scope:

// error
if let x = value...
// OK
let x = value
if …

Remember the double role of `if let`: 1. unwrap the optional, 2. define a
new variable, scoped to the `if` statement. We have 1, but not 2, and this
is the gist of this thread.

Gwendal