sscarduzio
sscarduzio

Reputation: 6188

How to execute a closure on an optional type without unwrapping it?

I have a Swift optional type and I want to chain a transformation to it (map) and eventually pass it to a closure that prints it.

I want avoid unwrapping (using !) the optional type because I don't want to execute the closure unless we have something inside the optional.

In Scala we use foreach as a map that returns Unit.

val oi = Option(2)
oi.map(_+1).foreach(println)

In Swift I get only until the increment (and the conversion to string)

let oi:Int? = 1
let oiplus = oi.map({$0 + 1}).map({String($0)})
// now we have "2"

Now how do I give this to print() only in the case the optional is not nil?

Upvotes: 3

Views: 434

Answers (2)

Rob Napier
Rob Napier

Reputation: 299355

While I usually do not recommend using map for side-effects, in this case I believe it's the cleanest Swift.

_ = oiplus.map{ print($0) }

There is now a SequenceType.forEach method that is explicitly for this problem, but Optional is not a SequenceType. You could of course extend Optional to add a forEach-like method (I'd probably call it apply in Swift).

It's worth noting that this just avoids the if-lit, which is barely any longer:

if let o = oiplus { print(o) }

But it admittedly doesn't chain as nicely as:

_ = oi
    .map { $0 + 1 }
    .map { print($0) }

See also http://www.openradar.me/23247992, which is basically Gabriele's answer.

Upvotes: 2

Gabriele Petronella
Gabriele Petronella

Reputation: 108101

I would just map over it

let oi: Int? = 1
let oiplus = oi.map{ $0 + 1 }.map { String($0) }
oiplus.map { print($0) }

Arguably not nice to map for producing a side-effect, but there's no foreach for Optional in Swift. This also produces an unused result warning in Swift 2.

If the semantic (and the warning) bug you, you can always define your own foreach for Optional. It's pretty straightforward:

extension Optional {
  func forEach(f: Wrapped -> Void) {
    switch self {
    case .None: ()
    case let .Some(w): f(w)
    }
  }
}

and then

let oi: Int? = 1
let oiplus = oi.map{ $0 + 1 }.map { String($0) }
oiplus.forEach { print($0) }

which resembles scala as much as possible.

Upvotes: 5

Related Questions