Optional Argument Chaining

It’s worth mentioning that the problem this thread is discussing can be generalized to idioms / applicative. The specific case here is for Optional but many other types could benefit from an elegant syntactic solution to this problem. It might be worth exploring a more general solution. Here’s a link to how this is handled in Idris: Interfaces — Idris 1.3.3 documentation.

Matthew

Just want to +1 a more general, less "Optional"-specific solution. You can do a ton of interesting things with applicative structure.

Chris L had a beautiful solution for an "Unwrappable" protocol that allowed all of the optional sugar to be extended to any type that had a biased `Wrapped` item, allowing it to be used with `Either`, `Wrapped`, etc as well as form the basis for `Optional` itself.

protocol Unwrappable {
    associatedtype Element
    func unwrap() -> Element?
}

I have a proposal around somewhere.(1)

-- E
(1) But then again, I have a lot of those

···

On Dec 13, 2017, at 10:20 AM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 11, 2017, at 1:08 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Failures from "Optional" and "throws" operate sequentially and halt immediately: the first "nil" or "throw" encountered prevents all work chained from it[1]. Another upcoming sequential operation that's worth thinking about in this discussion: "async"/"await".

Applicative structures throw away that sequential constraint. Let's consider some fun things that happen when we take it away in a couple of the examples above.

If "throws" were applicative, you could accumulate a bunch of errors at once.

   do {
     // made-up syntax
     let user = User(|name: try validate(name: name), email: try validate(email: email)|)
   } catch {
     print(error) // .manyErrors(["name is too short", "email is invalid"])
   }

Currently, the above would halt on the first error.

If "async"/"await" were applicative, you could fire off a bunch of asynchronous requests in parallel.

   let homepageData = HomepageData(|await fetchCurrentUser(), await fetchProducts()|)

In the proposed version of "async"/"await", the above would block on "fetchCurrentUser()" and only call "fetchProducts()" after the response.

"Optional" would get to use that same sugar! An example from the original email:

   getPostageEstimate(|source: john.address, destination: alice.address, weight 2.0|)

--
[1]: "Optional" and "throws" are monadic structures and "flatMap" is the abstraction of this sequential operation.

Stephen

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

In addition to being unspecific, how would you access an optional closure? Since trailing operators can’t be separated with whitespace, how do you differentiate two trailing “?” from a “??” operator?

i.e.

let a:Int? = ...
let b:Int? = ...
let someFunction:((Int, Int)->(Int))? = ...
let c = someFunction??(a, b)

It seems like it’s trying to provide a fallback of (Int?, Int?) instead of Int?.
I’m on the side of using the postfix “?” on the specific optional values, not on the function name. It has the benefit of not being confusing to someone new approaching the language because they are adjacent to the thing to change instead of in some other location.

In response to other criticisms, it seems very odd to me that the question of evaluation order of arguments is a serious problem for the original idea.

···

On Dec 12, 2017, at 2:32 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I propose that we do not add a suffix ? to the arguments because it does not really signal that the whole function may return an optional value. This is especially not convenient in a nested scenario like mentioned by others in previous posts. Instead we should reuse the inifix ? on functions, subscripts and initializer. This way we can signal that the function might fail if one of the provided optional arguments could not be unwrapped where the function expects a non-optional argument. If the return type was already an optional, we do not generate a nested optional, because there is no real reason for us to do so. All arguments can simply be passed to the function with an infix ? in their current form. If all non-optional parameter types match with the argument types - optional parameter types can safely be ignored - we generate an error, thus we don’t need an infix ?, otherwise the conversion happens implicitly. This approach removes the need for explicit argument annotations, which is IMHO boilerplate in first place. It also provides a hint to the reader that some of the passed arguments should be unwrapped to succeed. In every chase the ? is always close to the parameter list ?(_:_:...).

Here are some simple examples to consider:

func first(one: A, two: B, three: C?) -> D { ... }
func second(one: A, two: B, three: C?) -> D? { ... }
————————————————————————————————————————————————

// error when adding infix `?` if all arguments match the types
first?(one: a, two: b, three: c) // c can be both optional and non-optional
————————————————————————————————————————————————

// no need for infix `?`
first(one: a, two: b, three: optionalC) // returns `D`
————————————————————————————————————————————————

// sugared
first?(one: optionalA, two: b, three: c) // returns `D?` if succeed

// desugared
{ () -> D? in
    if let someA = optionalA {
        return first(one: someA, two: b, three: c)
    } else {
        return nil
    }
}()
————————————————————————————————————————————————

// sugared
first?(one: optionalA, two: optionalB, three: optionalC) // returns `D?` if succeed

// desugared
{ () -> D? in
    if let someA = optionalA, let someB = optionalB {
        return first(one: someA, two: someB, three: optionalC)
    } else {
    return nil
    }
}()
————————————————————————————————————————————————

// error when adding infix `?` if all arguments match the types
second?(one: a, two: b, three: c) // c can be both optional and non-optional
————————————————————————————————————————————————

// no need for infix `?`
second(one: a, two: b, three: optionalC) // returns `D?`
————————————————————————————————————————————————

// sugared
second?(one: optionalA, two: b, three: c) // returns `D?` if succeed - no need for nested optionals!

// desugared
{ () -> D? in
    if let someA = optionalA {
        return second(one: someA, two: b, three: c)
    } else {
        return nil
    }
}()
————————————————————————————————————————————————

// sugared
second?(one: optionalA, two: optionalB, three: optionalC) // returns `D?` if succeed - no need for nested optionals!

// desugared
{ () -> D? in
    if let someA = optionalA, let someB = optionalB {
        return second(one: someA, two: someB, three: optionalC)
    } else {
        return nil
    }
}()
Please note that this can only work with non-operator functions and initializers. Operator functions cannot be referenced as normal functions yet and optional subscripts would require a source breaking change to align with that behaviour.

If I missed something where optional func from Objective-C results into incompatibility with this approach, please fell free to correct me. From my point of view I don’t see how this additional behaviour could break optional func.

Am 12. Dezember 2017 um 19:47:25, Jared Khan via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Even this small example I think this is still a little fiddly. If we add another required parameter to the Markdown initializer:
let readme = String(contentsOfFile: “README.md”).flatMap { Markdown(string: $0, flavor: .github) }
this starts to feel a little inside-out and crufty to me.

On 12 Dec 2017, at 05:54, Félix Cloutier <felixcloutier@icloud.com <mailto:felixcloutier@icloud.com>> wrote:

You talk about flatMap without giving an example. The readme isn't that bad with it, IMO:

if let readme = String(contentsOfFile: "README.md").flatMap(Markdown) {
// use contents here
}

That doesn't work when you need multiple optional parameters. In my own experience, that hasn't been a huge problem, though.

Félix

Le 11 déc. 2017 à 08:30, Jared Khan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Hi all,

I'd like to propose a syntax addition that acts to ease some things that I believe should fall under the umbrella of 'optional chaining'. Optional chaining allows us to access the properties of an optional value and return nil if any link in that chain breaks. I propose we introduce syntax to allow similar chaining when passing optional valued parameters to functions that expect that parameter to be non-optional.

The example below is taken from a project I'm working on at the moment:

// Current
let readme: Markdown?
if let rawMarkdown = String(contentsOfFile: "README.md") {
        readme = Markdown(string: rawMarkdown)
} else {
        readme = nil
}
In this example we want to perform an operation, the initialisation of a 'Markdown' type, with our raw text if it exists and get nil otherwise. This is rather verbose

I propose the following syntax for an alternative:

// Proposed alternative
let readme = Markdown(string: String(contentsOfFile: "README.md")?)

The ? is familiar in its use for optional chaining.

This would act like syntactic sugar for the flatMap method on Optional. For example:

(where `john` is of a `Person` type with a property `address: Address?`)
// func getZipCode(fromAddress address: Address) -> ZipCode
getZipCode(fromAddress: john.address?)

// Would be equivalent to…
john.address.flatMap {
        getZipCode($0)
}
An example with multiple parameters:

// func getPostageEstimate(source: Address, destination: Address, weight: Double) -> Int
getPostageEstimate(source: john.address?, destination: alice.address?, weight: 2.0)

// Equivalent to
john.address.flatMap { freshVar1 in
        alice.address.flatMap { freshVar2 in
                getPostageEstimate(source: freshVar1, destination: freshVar2, weight: 2.0)
        }
}

// Or equally:
{
        guard let freshVar1 = john.address,
                let freshVar2 = alice.address else {
                        return nil
        }

        return getPostageEstimate(source: freshVar1, destination: freshVar2, weight: 2.0)
}()
This would only be allowed when the parameter doesn’t already accept Optionals and when the chained value is in fact an optional. We’d want to consider emitting at least the following errors/warnings in the given scenarios:

let result = myFunc(3?)
// error: cannot use optional chaining on non-optional value of type 'Int'

// func myFunc2(x: String?) -> String
let result = myFunc2(x: john.address?)
// error: cannot use optional argument chaining on argument of optional type
let result = myFunc(nil?)
// warning: optional argument chaining with nil literal always results in nil

Seeking your thoughts on this idea, the specific syntax, and more use case examples.

Best,

Jared

_______________________________________________
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

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

I’d agree with that. That’s consistent with the “left-to-right and short-circuit at first failure” approach.

···

On 12 Dec 2017, at 15:33, Yuta Koshizawa <koher@koherent.org> wrote:

I think evaluating them in the same way as `try` calls is consistent.

f(g()?, h()?, i(), j()?)?
// like
try f(try g(), try h(), i(), try j())
foo(bar(x()?)) + y()?
// like
foo(bar(try x())) + (try y())

--
Yuta

2017-12-12 7:42 GMT+09:00 Slava Pestov via swift-evolution
<swift-evolution@swift.org>:

On Dec 11, 2017, at 2:41 PM, Jared Khan via swift-evolution >> <swift-evolution@swift.org> wrote:

I missed the previous threads! I’ve found one of the relevant threads here:
[swift-evolution] Proposal: Extend Optional Chaining to Function, Initializer, and Subscript Parameters

Thanks for this important point.

If you were to write this logic out by hand then you would short-circuit it
and this is analogous to current chaining behaviour so to me evaluating left
to right (as Swift usually does) and stopping at the first failed unwrap
would make sense. I wouldn’t necessarily say it’s intuitive but I don’t
think it’s really less intuitive than the current chaining behaviour.

I think it gets confusing when you have multiple levels of nested
expressions, eg

foo(bar(x?)) + y?

Slava

Jared

On 11 Dec 2017, at 19:28, Xiaodi Wu via swift-evolution >> <swift-evolution@swift.org> wrote:

This topic has been discussed at least two and maybe more times in the
past.. It’s hard for me to post links at the moment, but it should be
possible to find on Google.

One major challenge to this idea, for which no satisfactory answer has been
achieved after all these years, is the following issue:

f(g()?, h()?, i(), j()?)?

If g() evaluates to nil, is h() called or not? How about i(), which is not
failable? Since any function or property access can have side effects, in
what order are the arguments evaluated, and how does one reason about this
code flow?

To my mind, in the absence of an intuitive answer to the above—which does
not appear to be possible—this idea is not feasible.
On Mon, Dec 11, 2017 at 12:34 Magnus Ahltorp via swift-evolution >> <swift-evolution@swift.org> wrote:

12 Dec. 2017 02:58 Jared Khan <jaredkhan@me.com> wrote:

2. It felt natural to me. It’s analogous to the existing optional
chaining scenarios and composes nicely. I think it’s about as understandable
as existing chaining, a newbie would have to look it up to discover its
meaning. What are your thoughts on this particular syntax (ignoring 3.
momentarily)? Hopefully others in this thread can share their views too.

Chaining methods is linear, while nesting fills a similar purpose when we
use function calls. This of course affects the way existing Swift code is
written, but that is something we have to live with if we want to use
familiar syntax patterns. However, I think we have to consider this
difference in this case, since the syntax becomes more convoluted. Your
suggestion is definitely not as easy to read as the optional chaining
syntax, and maybe it can't be.

As for how common I’d expect it to be, it’s something I’ve run into
myself a few times. Again, I hope members of this list can give their view
on if this would be useful to them.

I don't have any real examples, but I certainly think that I have run into
it, so I'm quite open to solving the problem. For me, it is probably only a
matter of finding a syntax that is acceptable.

3. I’m not entirely sure what the grammar situation is yet but afaik ‘?’
has never been available as a postfix operator. Perhaps I’m missing your
point, could you demonstrate where it is allowed?

I did not expect that you would be able to answer that, it was more a
question directed to people who are more connected to the inner workings of
the parsing of Swift than I am. It is not allowed, but the error message is
not the one I expect, something that gives me a hint that it does have some
meaning early in the parsing.

/Magnus

_______________________________________________
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

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

That’s actually the cool part of merging optional func infix ? with the discussed problem. If you think how we could represent optional func in pure swift only with some sugar (and also the ability to write var function(label:label:) which we’ll get at some point) then we could do this:

protocol P {
    optional func foo(label: Int)
}

// compiler could synthesize this for you:
extension P {
    default var foo(label:): ((Int) -> Void)? { return nil }
}

(someInstance as P).foo?(label: 42)
default is from swift-evolution/XXXX-role-keywords.md at a260a33ca39676b41e0436c4dccdb78441308c13 · erica/swift-evolution · GitHub

Now back to your example, the answer becomes really easy:

let c = someFunction?(a, b)

This can then be desugared to:

let c = {
    if let someFunction = someFunction, let someA = a, let someB = b {
        return someFunction(someA, someB)
    } else {
        return nil
    }
}
In any case c would be Optional<Int>.

Am 12. Dezember 2017 um 20:42:20, Benjamin Spratling (bspratling@mac.com) schrieb:

In addition to being unspecific, how would you access an optional closure? Since trailing operators can’t be separated with whitespace, how do you differentiate two trailing “?” from a “??” operator?

i.e.

let a:Int? = ...
let b:Int? = ...
let someFunction:((Int, Int)->(Int))? = ...
let c = someFunction??(a, b)

It seems like it’s trying to provide a fallback of (Int?, Int?) instead of Int?.
I’m on the side of using the postfix “?” on the specific optional values, not on the function name. It has the benefit of not being confusing to someone new approaching the language because they are adjacent to the thing to change instead of in some other location.

In response to other criticisms, it seems very odd to me that the question of evaluation order of arguments is a serious problem for the original idea.

···

On Dec 12, 2017, at 2:32 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I propose that we do not add a suffix ? to the arguments because it does not really signal that the whole function may return an optional value. This is especially not convenient in a nested scenario like mentioned by others in previous posts. Instead we should reuse the inifix ? on functions, subscripts and initializer. This way we can signal that the function might fail if one of the provided optional arguments could not be unwrapped where the function expects a non-optional argument. If the return type was already an optional, we do not generate a nested optional, because there is no real reason for us to do so. All arguments can simply be passed to the function with an infix ? in their current form. If all non-optional parameter types match with the argument types - optional parameter types can safely be ignored - we generate an error, thus we don’t need an infix ?, otherwise the conversion happens implicitly. This approach removes the need for explicit argument annotations, which is IMHO boilerplate in first place. It also provides a hint to the reader that some of the passed arguments should be unwrapped to succeed. In every chase the ? is always close to the parameter list ?(_:_:...).

Here are some simple examples to consider:

func first(one: A, two: B, three: C?) -> D { ... }
func second(one: A, two: B, three: C?) -> D? { ... }
————————————————————————————————————————————————

// error when adding infix `?` if all arguments match the types
first?(one: a, two: b, three: c) // c can be both optional and non-optional
————————————————————————————————————————————————

// no need for infix `?`
first(one: a, two: b, three: optionalC) // returns `D`
————————————————————————————————————————————————

// sugared
first?(one: optionalA, two: b, three: c) // returns `D?` if succeed

// desugared
{ () -> D? in
    if let someA = optionalA {
        return first(one: someA, two: b, three: c)
    } else {
        return nil
    }
}()
————————————————————————————————————————————————

// sugared
first?(one: optionalA, two: optionalB, three: optionalC) // returns `D?` if succeed

// desugared
{ () -> D? in
    if let someA = optionalA, let someB = optionalB {
        return first(one: someA, two: someB, three: optionalC)
    } else {
    return nil
    }
}()
————————————————————————————————————————————————

// error when adding infix `?` if all arguments match the types
second?(one: a, two: b, three: c) // c can be both optional and non-optional
————————————————————————————————————————————————

// no need for infix `?`
second(one: a, two: b, three: optionalC) // returns `D?`
————————————————————————————————————————————————

// sugared
second?(one: optionalA, two: b, three: c) // returns `D?` if succeed - no need for nested optionals!

// desugared
{ () -> D? in
    if let someA = optionalA {
        return second(one: someA, two: b, three: c)
    } else {
        return nil
    }
}()
————————————————————————————————————————————————

// sugared
second?(one: optionalA, two: optionalB, three: optionalC) // returns `D?` if succeed - no need for nested optionals!

// desugared
{ () -> D? in
    if let someA = optionalA, let someB = optionalB {
        return second(one: someA, two: someB, three: optionalC)
    } else {
        return nil
    }
}()
Please note that this can only work with non-operator functions and initializers. Operator functions cannot be referenced as normal functions yet and optional subscripts would require a source breaking change to align with that behaviour.

If I missed something where optional func from Objective-C results into incompatibility with this approach, please fell free to correct me. From my point of view I don’t see how this additional behaviour could break optional func.

Am 12. Dezember 2017 um 19:47:25, Jared Khan via swift-evolution (swift-evolution@swift.org) schrieb:

Even this small example I think this is still a little fiddly. If we add another required parameter to the Markdown initializer:
let readme = String(contentsOfFile: “README.md”).flatMap { Markdown(string: $0, flavor: .github) }
this starts to feel a little inside-out and crufty to me.

On 12 Dec 2017, at 05:54, Félix Cloutier <felixcloutier@icloud.com> wrote:

You talk about flatMap without giving an example. The readme isn't that bad with it, IMO:

if let readme = String(contentsOfFile: "README.md").flatMap(Markdown) {
// use contents here
}

That doesn't work when you need multiple optional parameters. In my own experience, that hasn't been a huge problem, though.

Félix

Le 11 déc. 2017 à 08:30, Jared Khan via swift-evolution <swift-evolution@swift.org> a écrit :

Hi all,

I'd like to propose a syntax addition that acts to ease some things that I believe should fall under the umbrella of 'optional chaining'. Optional chaining allows us to access the properties of an optional value and return nil if any link in that chain breaks. I propose we introduce syntax to allow similar chaining when passing optional valued parameters to functions that expect that parameter to be non-optional.

The example below is taken from a project I'm working on at the moment:

// Current
let readme: Markdown?
if let rawMarkdown = String(contentsOfFile: "README.md") {
        readme = Markdown(string: rawMarkdown)
} else {
        readme = nil
}
In this example we want to perform an operation, the initialisation of a 'Markdown' type, with our raw text if it exists and get nil otherwise. This is rather verbose

I propose the following syntax for an alternative:

// Proposed alternative
let readme = Markdown(string: String(contentsOfFile: "README.md")?)

The ? is familiar in its use for optional chaining.

This would act like syntactic sugar for the flatMap method on Optional. For example:

(where `john` is of a `Person` type with a property `address: Address?`)
// func getZipCode(fromAddress address: Address) -> ZipCode
getZipCode(fromAddress: john.address?)

// Would be equivalent to…
john.address.flatMap {
        getZipCode($0)
}
An example with multiple parameters:

// func getPostageEstimate(source: Address, destination: Address, weight: Double) -> Int
getPostageEstimate(source: john.address?, destination: alice.address?, weight: 2.0)

// Equivalent to
john.address.flatMap { freshVar1 in
        alice.address.flatMap { freshVar2 in
                getPostageEstimate(source: freshVar1, destination: freshVar2, weight: 2.0)
        }
}

// Or equally:
{
        guard let freshVar1 = john.address,
                let freshVar2 = alice.address else {
                        return nil
        }

        return getPostageEstimate(source: freshVar1, destination: freshVar2, weight: 2.0)
}()
This would only be allowed when the parameter doesn’t already accept Optionals and when the chained value is in fact an optional. We’d want to consider emitting at least the following errors/warnings in the given scenarios:

let result = myFunc(3?)
// error: cannot use optional chaining on non-optional value of type 'Int'

// func myFunc2(x: String?) -> String
let result = myFunc2(x: john.address?)
// error: cannot use optional argument chaining on argument of optional type
let result = myFunc(nil?)
// warning: optional argument chaining with nil literal always results in nil

Seeking your thoughts on this idea, the specific syntax, and more use case examples.

Best,

Jared

_______________________________________________
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

To me the short syntax should be a ‘macro’ expansion (syntax sugar only), therefore option 1 in Nick’s example. I choose the syntax sugar option because it is easy to explain.

-- Howard.

···

On 12 Dec 2017, at 7:03 pm, Nick Keets via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Dec 12, 2017 at 12:42 AM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

I think it gets confusing when you have multiple levels of nested expressions, eg

foo(bar(x?)) + y?

Slava

I'm not sure we need to optimize much for complicated nested examples, as I don't think this is something that will happen a lot in practice. I imagine the most common scenario will be a single call with one or more arguments.

Having said that, we did receive 2 different reasonable answers on what the evaluation order would be for:
  f(a(), b?)

(1) It desugars to:

if let someB = b {
  f(a(), someB)
}

So a() doesn't get called if b is nil.

(2) "If you were to write this logic out by hand then you would short-circuit it and this is analogous to current chaining behaviour so to me evaluating left to right (as Swift usually does) and stopping at the first failed unwrap would make sense"

So a() gets called if b is nil.

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

+1 to this. I also like Adrian’s notation where the ? is after the name, but before the parameter list.

···

On Dec 12, 2017, at 7:33 AM, Yuta Koshizawa via swift-evolution <swift-evolution@swift.org> wrote:

I think evaluating them in the same way as `try` calls is consistent.

f(g()?, h()?, i(), j()?)?
// like
try f(try g(), try h(), i(), try j())
foo(bar(x()?)) + y()?
// like
foo(bar(try x())) + (try y())

--
Yuta

2017-12-12 7:42 GMT+09:00 Slava Pestov via swift-evolution
<swift-evolution@swift.org>:

On Dec 11, 2017, at 2:41 PM, Jared Khan via swift-evolution >> <swift-evolution@swift.org> wrote:

I missed the previous threads! I’ve found one of the relevant threads here:
[swift-evolution] Proposal: Extend Optional Chaining to Function, Initializer, and Subscript Parameters

Thanks for this important point.

If you were to write this logic out by hand then you would short-circuit it
and this is analogous to current chaining behaviour so to me evaluating left
to right (as Swift usually does) and stopping at the first failed unwrap
would make sense. I wouldn’t necessarily say it’s intuitive but I don’t
think it’s really less intuitive than the current chaining behaviour.

I think it gets confusing when you have multiple levels of nested
expressions, eg

foo(bar(x?)) + y?

Slava

Jared

On 11 Dec 2017, at 19:28, Xiaodi Wu via swift-evolution >> <swift-evolution@swift.org> wrote:

This topic has been discussed at least two and maybe more times in the
past.. It’s hard for me to post links at the moment, but it should be
possible to find on Google.

One major challenge to this idea, for which no satisfactory answer has been
achieved after all these years, is the following issue:

f(g()?, h()?, i(), j()?)?

If g() evaluates to nil, is h() called or not? How about i(), which is not
failable? Since any function or property access can have side effects, in
what order are the arguments evaluated, and how does one reason about this
code flow?

To my mind, in the absence of an intuitive answer to the above—which does
not appear to be possible—this idea is not feasible.
On Mon, Dec 11, 2017 at 12:34 Magnus Ahltorp via swift-evolution >> <swift-evolution@swift.org> wrote:

12 Dec. 2017 02:58 Jared Khan <jaredkhan@me.com> wrote:

2. It felt natural to me. It’s analogous to the existing optional
chaining scenarios and composes nicely. I think it’s about as understandable
as existing chaining, a newbie would have to look it up to discover its
meaning. What are your thoughts on this particular syntax (ignoring 3.
momentarily)? Hopefully others in this thread can share their views too.

Chaining methods is linear, while nesting fills a similar purpose when we
use function calls. This of course affects the way existing Swift code is
written, but that is something we have to live with if we want to use
familiar syntax patterns. However, I think we have to consider this
difference in this case, since the syntax becomes more convoluted. Your
suggestion is definitely not as easy to read as the optional chaining
syntax, and maybe it can't be.

As for how common I’d expect it to be, it’s something I’ve run into
myself a few times. Again, I hope members of this list can give their view
on if this would be useful to them.

I don't have any real examples, but I certainly think that I have run into
it, so I'm quite open to solving the problem. For me, it is probably only a
matter of finding a syntax that is acceptable.

3. I’m not entirely sure what the grammar situation is yet but afaik ‘?’
has never been available as a postfix operator. Perhaps I’m missing your
point, could you demonstrate where it is allowed?

I did not expect that you would be able to answer that, it was more a
question directed to people who are more connected to the inner workings of
the parsing of Swift than I am. It is not allowed, but the error message is
not the one I expect, something that gives me a hint that it does have some
meaning early in the parsing.

/Magnus

_______________________________________________
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

_______________________________________________
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

It would definitely be nice to make "Optional" sugar work for non-"Optional" types! (I think the goal for "async"/"await" is to make it work for any continuation, so it's basically Haskell "do" notation for Swift!)

I'm not sure the "Unwrappable" protocol can handle many of the examples I mentioned (accumulative errors, parallelism), though I wonder if it could be designed differently to do so. The applicative structure requires just 2 functions[1]:

    protocol Applicative<A>: Functor<A> {
      static func pure(_ value: A) -> Self
      static func <*> <B>(lhs: Self<(A) -> B>, rhs: Self) -> Self<B>
    }

Such a protocol isn't possible in Swift (yet), but it could unlock this kind of sugar for everyone. Here's "Optional" conformance:

    extension Optional: Applicative<Wrapped> {
      static func pure(_ value: Wrapped) -> Wrapped? {
        return value // promoted to Optional.some
      }

      static func <*> <B>(lhs: Optional<(Wrapped) -> B>, rhs: Optional) -> B? {
        guard let lhs = lhs, rhs = rhs else { return nil }
        return lhs(rhs)
      }
    }

We can't conform to such an "Applicative" protocol today, but we _can_ still write and use these functions in a concrete manner! (Delete the conformance and see!)

The original post in this thread had this example:

    getPostageEstimate(source: String, destination: String, weight: Double)

If we were dealing with this function in the world of optional arguments, here's how we could use our abstraction:

    Optional.pure(curry(getPostageEstimate)) <*> john.address <*> alice.address <*> pure(2.0)

It's not _too_ bad, though it's kinda noisy, we have to maintain a bunch of custom code, we have to curry "getPostageEstimate" before passing it through, we lose our argument labels, and we're living in custom operator world, which can be disconcerting at first.

If Swift provided sugar over this structure, we'd merely need to do this:

    getPostageEstimate(|source: john.address, destination: alice.address, weight: 2.0|)

What's even neater is we can use this same format for types that wrap values in other ways! Let's say the addresses are coming from untrusted sources and need to be validated:

    getPostageEstimate(|source: try validateAddr(john), destination: try validateAddr(alice), weight: 2.0|)

If both addresses are invalid and "throw", we could get both errors and render them at once to our end user.

Another example: what if we want to get the postage estimate for two addresses that we need to fetch asynchronously:

    getPostageEstimate(|source: await myAddress(), destination: await theirAddress(), weight: 2.0|)

Such a syntax allows those requests to run in parallel and not block :)

In these examples, "Optional" promotion becomes applicative promotion ("2.0" is getting wrapped automatically), which might open a can of worms but it's a fun can to think about!

···

On Dec 13, 2017, at 9:53 PM, Erica Sadun <erica@ericasadun.com> wrote:

Chris L had a beautiful solution for an "Unwrappable" protocol that allowed all of the optional sugar to be extended to any type that had a biased `Wrapped` item, allowing it to be used with `Either`, `Wrapped`, etc as well as form the basis for `Optional` itself.

protocol Unwrappable {
  associatedtype Element
  func unwrap() -> Element?
}

--
[1]: Well, technically it also requires "map", since any applicative is a functor, and it also requires that it follows some math laws.

Stephen

Chris L had a beautiful solution for an "Unwrappable" protocol that allowed all of the optional sugar to be extended to any type that had a biased `Wrapped` item, allowing it to be used with `Either`, `Wrapped`, etc as well as form the basis for `Optional` itself.

protocol Unwrappable {
associatedtype Element
func unwrap() -> Element?
}

It would definitely be nice to make "Optional" sugar work for non-"Optional" types! (I think the goal for "async"/"await" is to make it work for any continuation, so it's basically Haskell "do" notation for Swift!)

I'm not sure the "Unwrappable" protocol can handle many of the examples I mentioned (accumulative errors, parallelism), though I wonder if it could be designed differently to do so. The applicative structure requires just 2 functions[1]:

   protocol Applicative<A>: Functor<A> {
     static func pure(_ value: A) -> Self
     static func <*> <B>(lhs: Self<(A) -> B>, rhs: Self) -> Self<B>
   }

Such a protocol isn't possible in Swift (yet),

Thanks for jumping in and elaborating on a more general approach! I don’t want to sidetrack the thread, but it actually is possible to encode higher-kindred types and protocols requiring them in Swift today. It’s a bit clunky and requires some boilerplate but the technique is worth knowing. Emulating HKT in Swift · GitHub

Some Kotlin folks have created a pretty robust FP library using the same technique: http://kategory.io/\.

···

Sent from my iPad
On Dec 13, 2017, at 11:13 PM, Stephen Celis <stephen.celis@gmail.com> wrote:

On Dec 13, 2017, at 9:53 PM, Erica Sadun <erica@ericasadun.com> wrote:

but it could unlock this kind of sugar for everyone. Here's "Optional" conformance:

   extension Optional: Applicative<Wrapped> {
     static func pure(_ value: Wrapped) -> Wrapped? {
       return value // promoted to Optional.some
     }

     static func <*> <B>(lhs: Optional<(Wrapped) -> B>, rhs: Optional) -> B? {
       guard let lhs = lhs, rhs = rhs else { return nil }
       return lhs(rhs)
     }
   }

We can't conform to such an "Applicative" protocol today, but we _can_ still write and use these functions in a concrete manner! (Delete the conformance and see!)

The original post in this thread had this example:

   getPostageEstimate(source: String, destination: String, weight: Double)

If we were dealing with this function in the world of optional arguments, here's how we could use our abstraction:

   Optional.pure(curry(getPostageEstimate)) <*> john.address <*> alice.address <*> pure(2.0)

It's not _too_ bad, though it's kinda noisy, we have to maintain a bunch of custom code, we have to curry "getPostageEstimate" before passing it through, we lose our argument labels, and we're living in custom operator world, which can be disconcerting at first.

If Swift provided sugar over this structure, we'd merely need to do this:

   getPostageEstimate(|source: john.address, destination: alice.address, weight: 2.0|)

What's even neater is we can use this same format for types that wrap values in other ways! Let's say the addresses are coming from untrusted sources and need to be validated:

   getPostageEstimate(|source: try validateAddr(john), destination: try validateAddr(alice), weight: 2.0|)

If both addresses are invalid and "throw", we could get both errors and render them at once to our end user.

Another example: what if we want to get the postage estimate for two addresses that we need to fetch asynchronously:

   getPostageEstimate(|source: await myAddress(), destination: await theirAddress(), weight: 2.0|)

Such a syntax allows those requests to run in parallel and not block :)

In these examples, "Optional" promotion becomes applicative promotion ("2.0" is getting wrapped automatically), which might open a can of worms but it's a fun can to think about!

--
[1]: Well, technically it also requires "map", since any applicative is a functor, and it also requires that it follows some math laws.

Stephen

I do not feel new syntax is needed for this.
You can do this currently:

postfix operator ¿

extension Optional {
    struct UnwrapError: Error {}
    static postfix func ¿(_ value: Optional) throws -> Wrapped {
        if let value = value {
            return value
        } else {
            throw UnwrapError()
        }
    }
}

Given function f: (Foo, Foo, Foo) -> Void
and functions a, b, c: () -> Foo?
you would then chain the calls like

try? f(a()¿, b()¿, c()¿)

This is hardly more verbose than

f(a()?, b()?, c()?)

At least on mac keyboard ¿ is not hard to type either.
Also everyone is free to bikeshed the operator to their liking.

If special syntax were to be added for this, then in my opinion
the behaviour should mimic this; i.e. evaluate arguments left-to-right,
abort on first nil.

-Pertti

While it's definitely worth knowing, it's not a really usable substitute for higher-kinded types in production code, and still requires a lot of code generation, which is eased in Kotlin thanks to its annotation/metaprogramming features.

Event the Kategory people recognized that the emulation approach is a temporary solution, waiting for the approval of a proposal for the addition of HKTs to Kotlin itself.

Unfortunately, if we don't get HKTs in Swift we're simply going to miss on a barrage of extremely useful abstractions, upon that many contemporary languages have started to rely, because they are simply the best known solutions to many problems in many contexts.

The huge amount of work that's been done in academia in the last 15 years (and it's still going) about applicatives - and also profunctor optics, another thing that requires HKTs - is mostly going to elude Swift due to its crucial lack of expressivity.

We're on the right track with the approval of the conditional conformance proposal, but my fear is that the ABI stability requirement for Swift 5 is going to lock the language interfaces in a state where it's going to be impossible for these kinds of sophistications to be added to the language at a later stage... I hope to be proven wrong here.

Elviro

···

Il giorno 14 dic 2017, alle ore 15:40, Matthew Johnson via swift-evolution <swift-evolution@swift.org> ha scritto:

Thanks for jumping in and elaborating on a more general approach! I don’t want to sidetrack the thread, but it actually is possible to encode higher-kindred types and protocols requiring them in Swift today. It’s a bit clunky and requires some boilerplate but the technique is worth knowing. Emulating HKT in Swift · GitHub

Some Kotlin folks have created a pretty robust FP library using the same technique: http://kategory.io/\.

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

+1

-Thorsten

···

Am 18.12.2017 um 08:42 schrieb Elviro Rocca via swift-evolution <swift-evolution@swift.org>:

While it's definitely worth knowing, it's not a really usable substitute for higher-kinded types in production code, and still requires a lot of code generation, which is eased in Kotlin thanks to its annotation/metaprogramming features.

Event the Kategory people recognized that the emulation approach is a temporary solution, waiting for the approval of a proposal for the addition of HKTs to Kotlin itself.

Unfortunately, if we don't get HKTs in Swift we're simply going to miss on a barrage of extremely useful abstractions, upon that many contemporary languages have started to rely, because they are simply the best known solutions to many problems in many contexts.

The huge amount of work that's been done in academia in the last 15 years (and it's still going) about applicatives - and also profunctor optics, another thing that requires HKTs - is mostly going to elude Swift due to its crucial lack of expressivity.

We're on the right track with the approval of the conditional conformance proposal, but my fear is that the ABI stability requirement for Swift 5 is going to lock the language interfaces in a state where it's going to be impossible for these kinds of sophistications to be added to the language at a later stage... I hope to be proven wrong here.

Elviro

Il giorno 14 dic 2017, alle ore 15:40, Matthew Johnson via swift-evolution <swift-evolution@swift.org> ha scritto:

Thanks for jumping in and elaborating on a more general approach! I don’t want to sidetrack the thread, but it actually is possible to encode higher-kindred types and protocols requiring them in Swift today. It’s a bit clunky and requires some boilerplate but the technique is worth knowing. Emulating HKT in Swift · GitHub

Some Kotlin folks have created a pretty robust FP library using the same technique: http://kategory.io/\.

_______________________________________________
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