[swift-evolution] Optional Argument Chaining

Matthew Johnson matthew at anandabits.com
Thu Dec 14 08:40:19 CST 2017



Sent from my iPad

On Dec 13, 2017, at 11:13 PM, Stephen Celis <stephen.celis at gmail.com> wrote:

>> On Dec 13, 2017, at 9:53 PM, Erica Sadun <erica at 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?
>> }
> 
> 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.  https://gist.github.com/anandabits/f12a77c49fc002cf68a5f1f62a0ac9c4

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

> 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171214/2af4a1f8/attachment.html>


More information about the swift-evolution mailing list