Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tipos expressivos em Swift

Tipos expressivos em Swift

O type system de Swift é muito versátil e poderoso. Explorando noções de álgebra e “abusando” dos generics conseguimos expressar muitas das regras de negócio das nossas aplicações nas definições dos nossos tipos, impedindo situações de negócio impossíveis em tempo de compilação e prevenindo mais bugs em tempo de execução. Vamos entender como podemos ser mais criativos nas nossas definições de tipo usando conceitos emprestados de linguagens puramente funcionais e algumas artimanhas.

Fellipe Caetano

December 11, 2018
Tweet

More Decks by Fellipe Caetano

Other Decks in Programming

Transcript

  1. struct SearchResultsState { let data: [Event] let isLoading: Bool let

    error: Error? } func render(state: SearchResultsState) { // ? }
  2. func render(state: SearchResultsState) { // ? } • Sempre que

    o array de eventos for vazio, deve-se mostrar uma mensagem? • Se o array não está vazio mas dados sendo carregados, o indicador de atividade tem precedência sobre a mensagem? • A mensagem de erro deve ser exibida sempre que o carregamento falha ou somente se o array de eventos está vazio?
  3. Structs são product types A quantidade de instâncias possíveis de

    uma struct é o produto das quantidades de instâncias possíveis dos tipos envolvidos em sua definição. struct SearchResultsState { let data: [Event] let isLoading: Bool let error: Error? } // |SearchResultsState| = |[Event]| * 2 * (1 + |Error|)
  4. A ambiguidade acontece porque o número de instâncias possíveis é

    maior do que o número de casos que fazem sentido para o negócio. Como reduzir o número de instâncias possíveis? Usando enums: enum SearchResultsState { case loaded(data: [Event]) case loading case failed(error: Error) }
  5. Enums são sum types A quantidade de instâncias possíveis de

    uma enum é a soma das quantidades de instâncias possíveis dos tipos envolvidos em sua definição. enum SearchResultsState { case loaded(data: [Event]) case loading case failed(error: Error) } // |SearchResultsState| = |[Event]| + 1 + |Error|
  6. func render(state: SearchResultsState) { switch state { case .loading: renderLoading()

    case let .loaded(data): renderLoaded(data: data) case let .failed(error): renderFailed(error: error) } }
  7. A ambiguidade desaparece pois o número de instâncias possíveis passa

    a ser compatível com o número de cenários previstos pelo negócio.
  8. Dessa forma, a pessoa programadora não tem mais dúvidas de

    como implementar a função render considerando todos os casos possíveis pois ela sabe enumerar todas as instâncias.
  9. Portanto, usar conceitos de álgebra ao definir seus tipos é

    uma forma de fazer com que cenários impossíveis sejam impossíveis de acontecer em tempo de execução, prevenindo bugs em tempo de compilação.
  10. Usar álgebra nas definições dos seus tipos é uma técnica

    que consegue atender diversos objetivos. Aqui, quisemos ilustrar dois deles: • Impedir que o domínio consiga representar situações impossíveis; • Usar as definições do domínio para expressar mais informações sobre o negócio.
  11. Além da álgebra, podemos usar um conceito chamado de phantom

    type para atingir os mesmos objetivos. Phantom types são tipos parametrizados (i. e. que usam generics) mas que não usam o parâmetro de tipo em suas definições. Veremos como melhorar parte do domínio de um app hipotético usando phantom types.
  12. struct Order { let identifier: String let purchaseDate: Date let

    tickets: [Ticket] } struct Ticket { let identifier: String let participant: Participant let value: Decimal }
  13. struct GetEditableParticipants: HTTPResource { private let orderIdentifier: String init (orderIdentifier:

    String) { self.orderIdentifier = orderIdentifier } var method: HTTPMethod { return .GET } var parameters: [String: Any] { return [ "order": orderIdentifier ] } }
  14. var request: GetEditableParticipants let order: Order = /* ... */

    let ticket: Ticket = /* ... */ // OK :) request = GetEditableParticipants(orderIdentifier: order.identifier) // OK :( request = GetEditableParticipants(orderIdentifier: ticket.identifier)
  15. O phantom type Tagged<Tag, RawValue> impede que instâncias inválidas como

    a segunda instância do exemplo anterior sejam criadas. struct Tagged<Tag, RawValue> { let rawValue: RawValue init (_ rawValue: RawValue) { self.rawValue = rawValue } }
  16. struct Order { typealias Id = Tagged<Order, String> let identifier:

    Id let purchaseDate: Date let tickets: [Ticket] } struct Ticket { typealias Id = Tagged<Ticket, String> let identifier: Id let participant: Participant let value: Decimal }
  17. struct GetEditableParticipants: HTTPResource { private let orderIdentifier: Order.Id init (orderIdentifier:

    Order.Id) { self.orderIdentifier = orderIdentifier } var method: HTTPMethod { return .GET } var parameters: [String: Any] { return [ "order": orderIdentifier.rawValue ] } }
  18. var request: GetEditableParticipants let order: Order = /* ... */

    let ticket: Ticket = /* ... */ // OK :) request = GetEditableParticipants(orderIdentifier: order.identifier) /* error: cannot convert value of type `Ticket.Id` (aka `Tagged<Ticket, String>`) to expected argument type `Order.Id` (aka `Tagged<Order, String>`) :) */ request = GetEditableParticipants(orderIdentifier: ticket.identifier)
  19. É importante frisar que o tipo Tagged<Tag, RawValue> é muito

    mais que um typealias. Além de adicionar semântica a um tipo primitivo como Int ou String, o tipo Tagged<Tag, RawValue> emprega o poder do compilador para impedir bugs em tempo de compilação.
  20. Fellipe Caetano Especialista em desenvolvimento iOS com 7+ anos de

    experiência implementando aplicações de nível enterprise para diversos segmentos. Atua como líder técnico de desenvolvimento iOS na Sympla. • E-mail: [email protected] • LinkedIn: fellipecaetano • Twitter: fellipecaetano_ • GitHub: fellipecaetano