Hi,
I want to discuss introducing something like Empty struct to standard library.
public struct Empty: Hashable, Codable {
public init() {}
}
Motivation
Currently there several ways to express concept of nothing if it is correct to say so.
- nil
- NSNull
- Void
- Never
There areNone
in Python,undefined
in JavaScript.
Sometimes problems occur with Void in generic context because Void can not conform to protocols like Hashable. See the examples:
generic state 1
public protocol SingularPriceVMStateCase {
associatedtype SingularPriceStateParams
static func ordinarySingularPrice(_ params: SingularPriceStateParams) -> Self
}
public protocol NotActiveVMStateCase {
associatedtype NotActiveStateParams
static func ordinaryNotActive(_ params: NotActiveStateParams) -> Self
}
public enum ProductVMState: SingularPriceVMStateCase, NotActiveVMStateCase {
case ordinarySingularPrice(SingularPriceStateParams)
case ordinaryNotActive(NotActiveStateParams)
struct SingularPriceStateParams { ... }
struct NotActiveStateParams { ... }
}
public enum GiftVMState: SingularPriceVMStateCase, NotActiveVMStateCase {
case ordinarySingularPrice(SingularPriceStateParams)
case ordinaryNotActive(Empty) // Void here causes compiler error as ViewModels must be Equatable
struct SingularPriceStateParams { ... }
}
generic state 2
public enum LoadingState<D> {
case isLoading
case dataLoaded(D)
case loadingError(text: String)
}
extension LoadingState: Equatable where D: Equatable {}
extension LoadingState: Hashable where D: Hashable {}
Either + equatable
// .left – button with text, .right – loader is shown instead of text
let singInButtonVM: Driver<Either<String, Empty>> = input.state.map { state in
switch state {
case .idle:
return .left("OAuth sign in") // button will be shown with text
case .makingConfig,
.waitingForAuthCode,
.authTokensRequest,
...:
return .right(Empty())
}
}
.distinctUntilChanged() // ! error: distinctUntilChanged operator requires element to be Equatable
PAT protocol with generic constraint
public protocol BindableView: AnyObject {
associatedtype Input
associatedtype Output: ViewOutputType
func getOutput() -> Output
func bindWith(input: Input)
}
// When view has no actions / callbacks Empty struct can be used.
extension Empty: ViewOutputType {}
// Otherwise a ceremony with creating empty types is needed for each view, like `struct LoyaltyViewOutput {}`, `struct ProfileNavigationViewOutput {}`...
empty network response
extension MoyaProvider {
/// when empty body is expected for status code 200
@discardableResult
public func requestVoid(target: Target,
completeOn completionQueue: DispatchQueue? = nil,
completion: @escaping (Result<Void, DecodingError>) -> Void) -> DataTask? {
let completionAdapter: (Result<Empty, DecodingError>) -> Void = { result in
completion(result.map { _ in Void() })
}
return request(target: target, completeOn: completionQueue) { responseResult in
let adaptedResponseResult = responseResult.map { ($0, JSONDecoder()) }
let finalResult: Result<Empty, DecodingError>
switch responseResult {
case .success:
finalResult = .success(Empty())
case .failure:
finalResult = Base.decodingFunction(adaptedResponseResult)
}
completionAdapter(finalResult)
}
}
}
All in all Empty
is a Void
analogue more suitable for generic context.