> On Dec 13, 2017, at 9:53 PM, Erica Sadun <er...@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), 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
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to