[swift-evolution] Add an ifPresent function to Optional

David Waite david at alkaline-solutions.com
Sun Mar 20 19:18:29 CDT 2016


> On Mar 20, 2016, at 11:52 AM, Erica Sadun via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Questions being raised in this discussion:
> 
> * Are the current stdlib names for optional map and flatMap misleading? 

Misleading makes it a harder question to answer. I can answer with my opinion though.

functional methods make sense on several ‘functional things’
1. An abstract generator of values, returning other generators of values which can be collected/reduced/folded into concrete results
2. A collection, returning concrete types to optimize the implementation as well as to eliminate the explicit the collection step (FWIW, I think map, etc should be moved off Sequence and onto Collection)
3. An unordered data set, returning unordered data sets, for processing in a parallel manner.
4. On optionals, where you are dealing with zero or one values

( Aside: To be clear, the above does represent two changes from the current state of things in Swift 3:
- I think LazySequence should be replaced by functional operations on generators
- I think the functional operations on Sequence should be moved to Collection if the goal is to have optimal/shortcut implementations )

You might be able to represent these all on a protocol hierarchy at some point in the future (if we get existential protocols in generics). 

In the meantime, there will certainly be duplication. This doesn’t meant that one location is proper and the others are not.

IMHO the goal is functional composition, so the kind of container (single v multiple elements, lazy or immediate, sequenced or unsequenced data, parallel or not) does not determine whether map/flatMap are present

> * Are the current stdlib functions for optional closure application appropriate and sufficient?
> 
>     @warn_unused_result
>     public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?

Sure, you are calling a function to transform the value(s) in a container or sequence, and returning the appropriate container/sequence in the new type. You *could* call this something like bind to match Haskell, but really that would just be a synonym. 

>     @warn_unused_result
>     public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?

Sure, you are calling a function to transform the value(s) in a container or sequence and returning a new container/sequence of some type of element, flattening that down, and returning the appropriate container/sequence in the new type.

One could say the lack of common types means that flatMap is too simplistic, and needs to support functions which return other types of containers, e.g.:

    @warn_unused_result
    public func flatMap<E,S:SequenceType where S.Generator.Element == E>(@noescape f: (Wrapped) throws -> S) rethrows -> [E]

which would return an empty array in the case of optional .none. 

> 
> Assume there could be up to three stdlib functions just for applying closures to .some-case 
> optionals. Would they look like this?
> 
> public func f1<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
> public func f2<U>(@noescape f: (Wrapped) throws -> U!) rethrows -> U!
> public func f3<U>(@noescape f: (Wrapped) throws -> U) rethrows -> Void
> 
> Right now f2/flatMap returns U?, not U or U!, and won't throw if nil, right? Should it
> stay that way or change to something that throws and returns a guaranteed value?
> Or maybe the current "f2" model (flatMap)  be discarded and replaced by an 
> "ifPresent"-like Void call?

The discussion in parallel about abolishing the ImplicitlyUnwrappedOptional is about representing IUO closer to what it really is - compiler + language rules which provide default runtime behavior for optionals.

So f2<U> is really

	public func f2<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?

AKA flatMap.

flatMap semantics are:
- if value is .none, return .none
- if value is .some, call function f with the .some value
   - if result of f is .none, return none
   - if result of f is .some value, return .some value

Even if you had IUO’s as a type, and had a variant that took a function that returned a IUO as a result, that would mean the function should behave closer to f1 aka map. 

There are three good reasons to have forEach even if you have map:

- generators or lazy collections will not evaluate until they consume. forEach is a consuming function, map may not be.
- if you are using functional style, map indicates the function is not meant to have side effects. forEach *only* makes sense for operations which do have side effects
- map returns a value while forEach does not. The creation of that value may have non-zero cost

> And regardless of which functions are included, what would the appropriate 
> names for each function style be?

I’m in favor of using map, flatMap, flatten, filter, etc. unless someone can beat the existing terms of art.

-DW

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160320/e1e45864/attachment.html>


More information about the swift-evolution mailing list