Add Empty struct

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 are None 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.

1 Like

Yeah, SE-0283 would be supposed to realize Void's conformance to Equatable, Comparable, and Hashable.

Should we

1 Like

Thanks for providing these links, I've missed this information.
I suppose it is better to wait for User-defined tuple conformances.

@Slava_Pestov can you share some details on progress of tuple conformances? Is it expected in Swift 5.10 / 5.11 / later...?

Some more questions:

  1. Currently this is valid: let instance: Void = (()) // empty tuple with empty tuple
    Will it be possible to do extension Tuple<Void>: Hashable or extension Void: Hashable {}? Are these declarations equivalent?
  2. Will this warning be eliminated?
func buildInstance<T>(_ builder: () -> T) -> T { builder() }
let instance = buildInstance { Void() } // warning: Constant 'instance' inferred to have type '()', which may be unexpected
  1. Will Void, (Void, Void) and (Void, Void, Void) have the same hashValue?

My 2 cents, I'd love to see a variadic struct Tuple<each T>: ExpressibleByTupleLiteral which together with parameter packs could permit typealias Void = Tuple<>, or at least I imagine it in such way.

This would also open the door for a single value (un-)labeled tuples.

1 Like

That’s not a nested tuple, that’s just spurious parentheses. The same way that both 2+3 and (2+3) are Int, both () and (()) are Void.

4 Likes