0

I'm trying to extend optionals into something readable, and achieved this so far:

@discardableResult
    func isNotNil(_ handler: (Wrapped) -> Void) -> Optional {
        switch self {
        case .some(let value):
            handler(value)
            return self
        case .none:
            return self
        }
    }
    
    @discardableResult
    func isNil(_ handler: () -> Void) -> Optional {
        switch self {
        case .some:
            return self
        case .none:
            handler()
            return self
        }
    }

So that I can call my functions on an optional such as:

viewModel?.title.isNotNil { _ in
   //do something
}.isNil {
  //handle error
}

The problem is, I want to reuse these functions to return specific types, which I'm not able to achieve or am missing something out. For example:

let vm: MyViewModel = dataSource?.heading.isNotNil {
  return MyViewModel(title: $0.title, subtitle: $0.subtitle)
}

I've been brainstorming on this and would love some help around this.

Thanks!

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
bhagat
  • 38
  • 4
  • 3
    Are you re-inventing `Optional.map`? E.g. `let vm = dataSource?.heading.map { MyViewModel(...) }` – Martin R Jul 08 '20 at 11:02

2 Answers2

0

Optional is generic and has an associated type that you can use in your extensions as well. It is called Wrapped.

Here is some sample code that you can paste into a Playground:

import UIKit

extension Optional {
    @discardableResult
    func isNil(_ handler: () -> Void) -> Wrapped? { // <-- We are returning Wrapped, wich is a generic concrete type
        switch self {
        case .some:
            return self
        case .none:
            handler()
            return self
        }
    }
}

let str: String? = nil

// The function is used and the return value is assigned to the variable handled
let handled = str.isNil { 
    print("Handling nil value")
}

print(type(of: handled))
// Prints "Optional<String>

Please also note that that what you are trying to build already exists mostly: https://developer.apple.com/documentation/swift/optional/1539476-map

You should probably just used Swift's map instead of your own implementation.

naglerrr
  • 2,809
  • 1
  • 12
  • 24
0

What you're doing in your example will raise errors. It boils down to

let vm: MyViewModel
if let heading = dataSource?.heading  {
   _ = MyViewModel(heading.title, heading.subtitle)
  vm = heading
}

So you're trying to assign heading to vm (which I am assuming are of different types) and you just construct and drop the MyViewModel you construct in the closure

What would be a better option is something along these lines:

func mapOptionalOrNot<T>(notNil: (Wrapped) -> T, isNil: () -> T) -> T {
    switch self {
    case .some(let value):
        return notNil(value)
    case .none:
        return isNil()
    }
}

And you could of course give both function default arguments so you can leave them out.

With Swift 5.3s new multi closures you could do something like

let vm: MyViewModel = dataSource?.heading.mapOptionalOrNot { value in
    // Map if we have a value
    return MyViewModel(title: value.title, subtitle: value.subtitle)
} isNil: {
    // Map if we don't have a value
    return MyViewModel(title: "Empty", subtitle: "Empty")
}
LotU
  • 36
  • 4