Hello Swift community
I have and idea to propose a splitMap
function for Sequence protocol. But first of all I want to gather some feedback and community thoughts. In the simplest form splitMap
is:
extension Collection {
public func splitMap<T1, T2, E: Error>(_ transform: (Element) throws(E) -> Either<T1, T2>) rethrows -> ([T1], [T2]) {
var groupA: [T1] = []
var groupB: [T2] = []
for element in self {
switch try transform(element) {
case .left(let a): groupA.append(a)
case .right(let b): groupB.append(b)
}
}
return (groupA, groupB)
}
}
extension ObservableType {
public func splitMap<U1, U2>(_ predicate: @escaping (Element) throws -> Either<U1, U2>)
-> (matches: Observable<U1>, nonMatches: Observable<U2>) {
let stream = map(predicate).share()
let hits = stream.compactMap { variant -> U1? in
switch variant {
case .left(let values): return values
case .right: return nil
}
}
let misses = stream.compactMap { variant -> U2? in
switch variant {
case .left: return nil
case .right(let element): return element
}
}
return (hits, misses)
}
}
For my own I use it with Swift.Collections, RxSwift, Combine. It can also be added to AsyncSequence / AsyncChannel.
Usage examples:
example 1
let (cellularTypes, unknownCarrierTypes) = сarrierTypesDict.values
.splitMap { carrierType -> Either<ReachabilityStatus.Cellular, String> in
switch carrierType {
case CTRadioAccessTechnologyGPRS:
return .left(.cellularGPRS)
case CTRadioAccessTechnologyEdge:
return .left(.cellularEDGE)
case CTRadioAccessTechnologyCDMA1x:
return .left(.cellular2G)
case CTRadioAccessTechnologyHSDPA:
return .left(.cellularHSDPA)
case CTRadioAccessTechnologyHSUPA:
return .left(.cellularHSUPA)
case CTRadioAccessTechnologyWCDMA,
CTRadioAccessTechnologyCDMAEVDORev0,
CTRadioAccessTechnologyCDMAEVDORevA,
CTRadioAccessTechnologyCDMAEVDORevB,
CTRadioAccessTechnologyeHRPD:
return .left(.cellular3G)
case CTRadioAccessTechnologyLTE:
return .left(.cellularLTE)
default:
switch carrierType {
case CTRadioAccessTechnologyNRNSA,
CTRadioAccessTechnologyNR:
return .left(.cellular5G)
default:
return .right(carrierType)
}
}
}
example 2
let (activeProducts, inactiveProducts) = accumulator.hashedProductVMs
.splitMap { rawViewModel -> Either<ProductVM, InactiveProductVM> in
switch rawViewModel.state {
case .ordinaryNotActive:
return .right(rawViewModel.copy(transformedState: InactiveProductVMState.ordinaryNotActive(Empty())))
case .ordinaryWithDiscountForAmount(let params):
return .left(rawViewModel.copy(transformedState: ProductVMState.ordinaryWithDiscountForAmount(params)))
case .ordinarySingularPrice(let params):
return .left(rawViewModel.copy(transformedState: ProductVMState.ordinarySingularPrice(params)))
case .gift(let params):
return .left(rawViewModel.copy(transformedState: ProductVMState.gift(params)))
}
}
example 3
let (certificatesInstances, failedCertificates) = certificates
.splitMap { certificateData -> Either<SecCertificate, Data> in
if let certificateInstance = SecCertificateCreateWithData(nil, certificateData as CFData) {
return .left(certificateInstance)
} else {
return .right(certificateData)
}
}
example 4 (Rx)
let (routeToOnlinePayment: Observable<(UInt64, NSDecimalNumber, PaymentInfo)>, routeToCashPayment: Observable<Void>) = responses.paymentKindUpdated
.filter { $0.isPaymentPossible }
.splitMap { paymentData -> Either<(UInt64, NSDecimalNumber, PaymentInfo), Void> in
if let paymentInfo = paymentData.paymentInfo {
return .left((paymentData.orderId, NSDecimalNumber(value: paymentData.totalPrice), paymentInfo))
} else {
return .right(Void())
}
}
example 5 (Rx)
let (oAuthCodeRecoverableError, oAuthCodeUnrecoverableError) = authCodeErrorEvent
.splitMap(AuthCodeErrorHelper.splitMapAuthCodeError(_:)) // `splitMapAuthCodeError(_:)` contains a lot of bolierplate
There are plenty of other examples but I think it's enough for now.
The topics to discuss are:
- is it needed for others? may be I often meet such cases because of my projects specifics
- should it be added to standard library, SwiftAlgorithms, Foundation or somewhere else?
- how and can it be implemented for Sequence with 3 and more generic parameters? Variadic generics don't suite for this, at least I don't know how to do it.
- is the name
splitMap
suitable or something else should be picked (e.g.partition
)