Kotlin "takeIf" statement equivalent in swift?

Viewed 508

I have this code very simple in kotlin which returns a value if a certain condition is satisfied, or null

val speed = location.speed.takeIf { it>=0 }

I'm trying to do the same in swift, but the only way I found is this:

let speed:Double?
if (location.speed>=0){
    speed = location.speed
}else{
    speed = nil
}

Is there any way more elegant?

5 Answers

There isn't something like that in Swift, so we need to find a way to write a takeIf function ourselves.

In Kotlin, takeIf is available on everything, as an extension function on all T:

inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?

In Swift, you can't write an extension on Any, so we can only make a global function:

func take<T>(_ value: T, if predicate: (T) throws -> Bool) rethrows -> T? {
    try predicate(value) ? value : nil
}

// example usage:
let x = Int.random(in: 0..<10)
let y = take(x, if: { $0 > 5 })

If you are creative enough to think of an operator, you can turn this into an infix operator, similar to how the Kotlin takeIf is in between the receiver and the predicate.

// I am not creative enough...
infix operator ???

func ???<T>(value: T, predicate: (T) throws -> Bool) rethrows -> T? {
    try predicate(value) ? value : nil
}

let a = Int.random(in: 0..<10)
let b = x ??? { $0 > 5 }

It's not built into the language, but you can add it.

public extension Optional {
  /// Transform `.some` into `.none`, if a condition fails.
  /// - Parameters:
  ///   - isSome: The condition that will result in `nil`, when evaluated to `false`.
  func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
    try flatMap { try isSome($0) ? $0 : nil }
  }
}
let speed = location.speed.filter { $0 >= 0 }

You can implement this as a protocol with a default implementation. Then just conform any type to this protocol.

protocol Takable {
    func takeIf(_ condition: (Self) throws -> Bool) rethrows -> Self?
}

extension Takable {
    func takeIf(_ condition: (Self) throws -> Bool) rethrows -> Self? {
        try condition(self) ? self : nil
    }
}

// Example:
struct Test: Takable {
    let val: Int
}

let t = Test(val: 1)

let a = t.takeIf { $0.val == 1 } // not nil
let b = t.takeIf { $0.val == 2 } // nil

Why not try

let speed: Double? = location.speed >= 0 ? location.speed : nil

Swift still supports ? : if checks, like Java, which are not supported by Kotlin, unless you write an if () else .

P.S.: Are you sure you want to return an optional and not 0 if the speed is 0?

You can make extension for Double, like this:

extension Double {
    func takeIf() -> Double? {
        if self >= 0 {
            return self
        } else {
            return nil
        }
    }
}
Related