[swift-evolution] [Pitch] KeyPath based map, flatMap, filter

Dave Abrahams dabrahams at apple.com
Mon Jul 10 13:01:53 CDT 2017

on Sun Jul 09 2017, Brent Royal-Gordon <brent-AT-architechies.com> wrote:

> But however we achieve it, I think a spoonful of
> syntactic sugar would help the medicine go down.

Let me be clear: syntactic sugar matters.  Otherwise, we'd all be
programming directly in LLVM IR.  It's just a question of what you have
to pay to get it.

>> By the way, if you're worried about whether subtyping will fly, I've
>> recently been thinking there might be a role for a “promotion” operator
>> that enables lossless “almost-implicit” conversions, e.g.:
>>    someNumber^      is equivalent to    numericCast(someNumber)
>>    \.someKeyPath^   is equivalent to    { $0\.someKeyPath }
>>    someSubstring^   is equivalent to    String(someSubstring)
>>    etc.
> I actually played with something like this years ago (pre-open source,
> IIRC), but I used `^` as a prefix operator and made it support only
> widening conversions. But it was old code, and redoing it nerd-sniped
> me so hard that I kind of ended up making a whole GitHub project from
> it: <https://github.com/brentdax/Upconvert>
> The main component is an `Upconvertible` protocol which encapsulates
> the conversion. That works really well in some ways, but it also
> creates some important limitations:
> 1. I had trouble incorporating downconversions in a reasonable
> way. Key paths in particular would require either compiler support or
> some really hacky, fragile code that opened up the closure context and
> pulled out the KeyPath object.
> 2. There's no good way to support more than one upconversion from a
> single type. (For instance, you can't make `UInt16` upconvert to both
> `Uint32` and `Int32`.)
> 3. Even if #2 were somehow fixed, you still can't make all
> `LosslessStringConvertible` types conform to `Upconvertible`.
> 4. Can't upconvert from a structural type, of course.

AFAICT, all of the above come down to having tried to build this idiom
around a protocol with an associated type.  I think of it as a
special-case syntactic shortcut for “value-preserving conversion to
deduced type.” Just overload the operator and be done with it.  Maybe
protocols like BinaryInteger should have a generic operator, but this
doesn't deserve a protocol of its own.

> 5. I wanted to support passing through any number of valid
> upconversions with a single `^` operator, but the only way I could
> find to do that was to overload the operator with a two-step version,
> a three-step version, etc.
> 6. Upconverting a `\.keyPath` expression caused an ambiguity error; I
> had to overload the operator to make it favor `KeyPath`. (Workaround
> code:
> https://github.com/brentdax/Upconvert/blob/master/Upconvert/Conformances/KeyPath.swift#L25)

Meh; that's a fact of life when overloading.

> Several-to-all of these could be avoided with a built-in language feature.

IMO no language feature is needed for this.

> As for the ergonomics…well, `people.map(^\.name)` definitely feels
> better than the closure alternative. But it's something you have to
> learn is possible, and even if you knew about `^` in the context of
> (say) numeric conversions, I'm not sure people would think to try it
> there. It basically means you need to know about three slightly
> esoteric features instead of two; I'm not sure people will discover
> that.

Yes, but there's a trade-off between discoverability, and introducing
more implicit conversions, which will slow down the type checker and can
make errors harder to understand.  Note: I'm not arguing for either
approach in particular.  They're just available alternatives.


More information about the swift-evolution mailing list