How can I create an operator to implement error chaining? - swift

I want to implement the following operator »:
throwingFunction(arg: T?)».doStuff()
/* if throwingFunction throws an error:
print or log the error
else
returns an object having the doStuff() Method
OR
An Alternative design that I'm open to is
Instead of a throwing Error, the `throwingFunction()`
can be swapped out for a method that returns `Result`
OR
a custom Type, with a generic payload type.
*/
Here is an example of something similar to what I'm looking for. It is a custom implementation of optional chaining made possible using the KeyPath object (credit to Sergey Smagleev
).
precedencegroup Chaining {
associativity: left
}
infix operator ~> : Chaining
extension Optional {
static func ~><T>(value: Wrapped?, key: KeyPath<Wrapped, T> ) -> T? {
return value.map { $0[keyPath: key] }
}
static func ~><T>(value: Wrapped?, key: KeyPath<Wrapped, T?> ) -> T? {
return value.flatMap { $0[keyPath: key] }
}
}
struct Object {
let anotherOptionalObject: AnotherObject?
}
struct AnotherObject {
let value: String
}
let optionalObject: Object? = Object(anotherOptionalObject: AnotherObject(value: "Hello world"))
print(optionalObject~>\.anotherOptionalObject~>\.value) //this prints Optional("Hello world")
print(optionalObject?.anotherOptionalObject?.value) //this also prints Optional("Hello world")
Except, I want the implementation to provide an opportunity for me to handle the error by printing or logging it.

prefix and postfix are unary operators i.e. they only accept one operand, whereas an infix operator is a binary operator i.e. it accepts two operands.
So, static func »(value:key:) -> Preferred? is not correct because it's taking two operands whereas you have defined » as a postfix operator.

For some reason I get "» is not a postfix unary operator" when I don't use the escape syntax. But apart from that it seems to work.
precedencegroup Chaining {
associativity: left
}
infix operator » : Chaining
extension Result {
static func »<T>(value: Self, key: KeyPath<Success, T>) -> T? {
switch value {
case .success(let win):
return win[keyPath: key]
case .failure(let fail):
print(fail.localizedDescription)
return nil
}
}
}
// I included a custom type so that it could be customizable if needed.
enum Err<Wrapped> {
static func »<T>(value: Self, key: KeyPath<Wrapped, T>) -> T? {
switch value {
case .error(let err):
print(err.localizedDescription)
return nil
case .some(let wrapper):
return wrapper[keyPath: key]
}
}
case error(Error)
case some(Wrapped)
}
func errorWrapped() -> Err<String> {
.some("Hello World")
}
func pleaseWork() {
print(errorWrapped()»\.isEmpty)
}

Related

Swift Extension - Optional

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!
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")
}
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.

Optional extension for any types

I want to write Optional extension for any types.
My code for integer:
extension Optional where Wrapped == Int {
func ifNil<T>(default: T) -> T {
if self != nil {
return self as! T
}
return default
}
}
var tempInt: Int?
tempInt.ifNil(default: 2) // returns 2
tempInt = 5
tempInt.ifNil(default: 2) // returns 5
It works but it is Optional(Int) extension (Optional where Wrapped == Int), I want to use this extension for any types like Date, String, Double etc.
What are your suggestions?
The answer to your basic question is to just remove the where clause:
extension Optional {
// ... the rest is the same
func isNil<T>(value: T) -> T {
if self != nil {
return self as! T
}
return value
}
}
Now it applies to all Optionals.
But this code is quite broken. It crashes if T is not the same as Wrapped. So you would really mean a non-generic function that works on Wrapped:
extension Optional {
func isNil(value: Wrapped) -> Wrapped {
if self != nil {
return self! // `as!` is unnecessary
}
return value
}
}
But this is just an elaborate way of saying ?? (as matt points out)
extension Optional {
func isNil(value: Wrapped) -> Wrapped { self ?? value }
}
Except that ?? is much more powerful. It includes an autoclosure that avoids evaluating the default value unless it's actually used, and which can throw. It's also much more idiomatic Swift in most cases. You can find the source on github.
public func ?? <T>(optional: T?, defaultValue: #autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
But I can imagine cases where you might a method-based solution (they're weird, but maybe there are such cases). You can get that by just rewriting it as a method:
extension Optional {
public func value(or defaultValue: #autoclosure () throws -> Wrapped) rethrows -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
}
tempInt.value(or: 2)
Optional is already a generic. It already takes any type as its parameterized type. Its parameterized already has a name: Wrapped. Just say Wrapped instead of T. Your T is Wrapped.
extension Optional {
func isNil<Wrapped>(value: Wrapped) -> Wrapped {
if self != nil {
return self as! Wrapped
}
return value
}
}
If you really prefer T for some reason, use a type alias. It's only a name:
extension Optional {
typealias T = Wrapped
func isNil<T>(value: T) -> T {
if self != nil {
return self as! T
}
return value
}
}
But in any case your extension is completely unnecessary because this is what the nil-coalescing operator ?? already does.
var tempInt: Int?
tempInt ?? 2 /// returns 2
tempInt = 5
tempInt ?? 2 /// returns 5

Can I do something not `transfrom` in `map` and `flatMap` function?

Is Map function of Optional in Swift just used to transform?
If I want do something just Optional has some value, Can I Use map function? if not, why?
According to apples examples, we used map like this
let possibleNumber: Int? = Int("42")
possibleNumber.map { $0 * $0 }
Can I use it like this? : (If it's not proper, how to explain it)
func setImage(with data: Data?) {
data.flatMap { UIImage(data: $0) }
.map { imageView.image = $0 }
}
Furthermore map function should return a value, but why this function does not have any warning about not used of result ( such as result of call map{...} is unused ) ?
You certainly can do it, it's just not very conventional. When people see map, they have a pre-conceived expectation that it'll be doing a transformation. You're violating that expectation, but there's nothing technically "wrong" about it.
Personally, I prefer using this extension:
extension Optional {
/// An enum used to ensure that `ifNone` is never called before `ifSome`.
enum IfSomeResult {
case some
case none
func ifNone(_ closure: () throws -> Void) rethrows -> Void {
switch self {
case .some: return
case .none: try _ = closure()
}
}
}
#discardableResult
func ifSome(then closure: (Wrapped) throws -> Void) rethrows -> IfSomeResult {
if let wrapped = self {
try _ = closure(wrapped)
return IfSomeResult.some
}
else {
return IfSomeResult.none
}
}
func ifNone(then closure: () throws -> Void) rethrows -> Void {
if case nil = self { try _ = closure() }
}
}
And writing code like:
data.flatMap { UIImage(data: $0) }
.ifSome { imageView.image = $0 }
Why doesn't it warn about an unused value?
The closure is inferred to return Void (the empty tuple type, whose only value is the empty tuple, ()). The compiler never emits warnings about Void values being unused`. Here's an example:
Optional(123).map { _ in () }

Using Swift generics in a RxSwift getter function - various problems

Here's the function:
func registerFor<Element>(relayId id: String) -> Driver<Element>? {
guard let relay = relays[id] as? BehaviorRelay<Element> else { return nil }
return relay.asObservable()
.distinctUntilChanged { a, b in
return a != b
}.flatMapLatest { value in
return Observable.create { observer in
observer.on(.next(value))
return Disposables.create()
}
}.asDriver(onErrorJustReturn: Element())
}
The distinctUntilChanged line throws the following error:
Contextual closure type '(Element) throws -> _' expects 1 argument,
but 2 were used in closure body
The asDriver line throws the following error (of course):
Non-nominal type 'Element' does not support explicit initialization
Context: I have a class that ideally has a collection of BehaviorRelays of various types (Strings, Ints, etc). Element stands in generically for these types, but that creates two problems:
distinctUntilChanged insists of having a closure (eg: if this method returned Driver<String> it would be content simply to use distinctUntilChanged() but the generic Element makes it complain about missing a closure);
onErrorJustReturn requires a concrete value, but Element is generic.
The following "workaround" might work but I suspect there are better solutions
protocol Inii {
init()
}
func registerFor(relayId id: String, def: Inii.Type) -> Driver<Inii>? {
return relays[id]?.asObservable()
.distinctUntilChanged { _, _ in
return true
}.flatMapLatest { value in
return Observable.create { observer in
observer.on(.next(value))
return Disposables.create()
}
}.asDriver(onErrorJustReturn: def.init())
}
Although I'm still unsure what to put in the distinctUntilChanged closure.
Appendix A
I believe that the following is what is required if one is implementing the distinctUntilChanged closure for a non-generic type:
.distinctUntilChanged { previousValue, currentValue in
return previousValue == currentValue
}
However, when used with the generic Element the following error is still thrown:
Contextual closure type '(Inii) throws -> _' expects 1 argument,
but 2 were used in closure body
Appendix B
Here's another alternative with a slightly different problem:
protocol Inii {
init()
}
var relay = BehaviorRelay<String>(value: "")
func registerFor<Element>(def: Element.Type) -> Driver<Element> where Element: Inii {
return relay.asObservable()
.distinctUntilChanged { previousValue, currentValue in
return previousValue == currentValue
}.flatMapLatest { value in
return Observable.create { observer in
observer.on(.next(value))
return Disposables.create()
}
}.asDriver(onErrorJustReturn: def.init())
}
Error in this case being:
Member 'next' in 'Event<_>' produces result of type 'Event<Element>',
but context expects 'Event<_>'
at the observer.on line
You can use distinctUntilChanged() without a closure as long as Element conforms to Equatable:
protocol EmptyInit {
init()
}
func registerFor<Element>(relayId id: String) -> Driver<Element>? where Element: Equatable, Element: EmptyInit {
guard let relay = relays[id] as? BehaviorRelay<Element> else { return nil }
return relay.asObservable()
.distinctUntilChanged()
.flatMapLatest { value in
return Observable.create { observer in
observer.on(.next(value))
return Disposables.create()
}
}.asDriver(onErrorJustReturn: Element())
}

RxSwift unwrap optional handy function?

Currently I have created a function unwrapOptional to safely unwrap the optional input in the stream.
func unwrapOptional<T>(x: Optional<T>) -> Observable<T> {
return x.map(Observable.just) ?? Observable.empty()
}
let aOpt: String? = "aOpt"
_ = Observable.of(aOpt).flatMap(unwrapOptional).subscribeNext { x in print(x)}
let aNil: String? = nil
_ = Observable.of(aNil).flatMap(unwrapOptional).subscribeNext { x in print(x)}
let a: String = "a"
_ = Observable.of(a).flatMap(unwrapOptional).subscribeNext { x in print(x)}
// output
aOpt
a
What I want to archive is to create a handy function instead of using flatMap(unwrapOptional), for example
Observable.of(a).unwrapOptional()
Something I tried to do, but it never compiles...
extension ObservableType {
func unwrapOptional<O : ObservableConvertibleType>() -> RxSwift.Observable<O.E> {
return self.flatMap(unwrapOptional)
}
}
You want the unwrapOptional method to only work on observables that have optional type.
So you somehow have to constraint the Element of Observable to conform to the Optional protocol.
extension Observable where Element: OptionalType {
/// Returns an Observable where the nil values from the original Observable are
/// skipped
func unwrappedOptional() -> Observable<Element.Wrapped> {
return self.filter { $0.asOptional != nil }.map { $0.asOptional! }
}
}
Unfortunately, Swift does not define such a protocol (OptionalType). So you also need to define it yourself
/// Represent an optional value
///
/// This is needed to restrict our Observable extension to Observable that generate
/// .Next events with Optional payload
protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}
/// Implementation of the OptionalType protocol by the Optional type
extension Optional: OptionalType {
var asOptional: Wrapped? { return self }
}
checkout unwrap at https://github.com/RxSwiftCommunity/RxSwift-Ext :)
or https://github.com/RxSwiftCommunity/RxOptional
For now, you should use RxOptional for your personal needs
However, RxSwift-Ext will be growth exponentially in next 2-3 months :)
RxSwift now supports compactMap(). So, now you can do things like:
func unwrap(_ a: Observable<Int?>) -> Observable<Int> {
return a.compactMap { $0 }
}
Here's a version without needing OptionalType (from https://stackoverflow.com/a/36788483/13000)
extension Observable {
/// Returns an `Observable` where the nil values from the original `Observable` are skipped
func unwrap<T>() -> Observable<T> where Element == T? {
self
.filter { $0 != nil }
.map { $0! }
}
}