Por que os sindicatos discriminatórios estão associados à programação funcional?

30

Em muitos anos de programação OO, entendi o que são sindicatos discriminados, mas nunca senti muita falta deles. Eu tenho feito recentemente alguma programação funcional em C # e agora acho que desejo continuar com eles. Isso me deixa desconcertante, porque, diante disso, o conceito de uniões discriminadas parece bastante independente da dicotomia funcional / OO.

Existe algo inerente à programação funcional que torna os sindicatos discriminados mais úteis do que seria no OO, ou é que, ao me forçar a analisar o problema de uma maneira "melhor", simplesmente aprimorei meus padrões e agora exijo uma melhor modelo?

Andy
fonte
O problema expressão en.wikipedia.org/wiki/Expression_problem pode ser relevante
XJI
Não é realmente uma resposta adequada à pergunta, por isso vou responder como um comentário, mas a linguagem de programação do Ceilão tem tipos de união e eles aparentemente estão se arrastando para outras linguagens OO / paradigma misto - TypeScript e Scala me vêm à mente. Também enums da linguagem Java podem ser usadas como uma espécie de implementação de uniões discriminadas.
Roland Tepp

Respostas:

45

As uniões discriminadas realmente brilham em conjunto com a correspondência de padrões, onde você seleciona comportamentos diferentes, dependendo dos casos. Mas esse padrão é fundamentalmente antitético aos princípios puros de OO.

Em OO puro, as diferenças de comportamento devem ser definidas pelos próprios tipos (objetos) e encapsuladas. Portanto, a equivalência à correspondência de padrões seria chamar um único método no próprio objeto, que será sobrecarregado pelos subtipos em questão para definir um comportamento diferente. Inspecionar o tipo de um objeto de fora (que é o que a correspondência de padrões faz) é considerado um antipadrão.

A diferença fundamental é que dados e comportamento são separados na programação funcional, enquanto dados e comportamento são encapsulados juntos no OO.

Esta é a razão histórica. Uma linguagem como C # está se desenvolvendo de uma linguagem OO clássica para uma linguagem de múltiplos paradigmas, incorporando cada vez mais recursos de funções.

JacquesB
fonte
6
Um tipo de união / soma discriminado não é como uma árvore de herança ou várias classes implementando uma interface; é um tipo com muitos tipos de valores. Eles também não impedem o encapsulamento; o cliente não precisa saber que você tem vários tipos de valores. Considere uma lista vinculada, que pode ser um nó vazio ou um valor e uma referência a outro nó. Sem tipos de soma, você acaba cortando uma união discriminada de qualquer maneira, tendo variáveis ​​para o valor e referência, mas tratando um objeto com uma referência nula como um nó vazio e fingindo que a variável value não existe.
Doval
2
Em geral, você acaba incluindo as variáveis ​​para todos os tipos possíveis de valores em uma classe, além de algum tipo de sinalizador ou enum para informar qual tipo de valor é essa instância e dançando em torno das variáveis ​​que não correspondem a essa tipo.
Doval
7
@Doval Existe uma codificação "padrão" de um tipo de dados algébrico em classes, tendo uma classe básica de interface / resumo representando o tipo geral e tendo uma subclasse para cada caso do tipo. Para lidar com a correspondência de padrão, você tem um método que leva uma função para cada caso, de modo que para uma lista que você teria sobre a interface de nível superior, List<A>o método B Match<B>(B nil, Func<A,List<A>,B> cons). Por exemplo, esse é exatamente o padrão que o Smalltalk usa para booleanos. Isso também é basicamente como Scala lida com isso. O fato de várias classes serem usadas é um detalhe de implementação que não precisa ser exposto.
Derek Elkins
3
@Doval: A verificação explícita de referências nulas também não é considerada OO idiomática.
precisa saber é o seguinte
@JacquesB A verificação nula é um detalhe de implementação. Para o cliente da lista vinculada, geralmente há um isEmptymétodo que verifica se a referência ao próximo nó é nula.
Doval
35

Tendo programado em Pascal e Ada antes de aprender programação funcional, não associo uniões discriminadas a programação funcional.

Os sindicatos discriminados são, de alguma forma, o dual da herança. O primeiro permite adicionar operações facilmente em um conjunto fixo de tipos (aqueles na união), e a herança permite adicionar tipos facilmente com um conjunto fixo de operações. (Como adicionar facilmente ambos é chamado de problema de expressão ; esse é um problema especialmente difícil para idiomas com um sistema de tipo estático.)

Devido à ênfase do OO nos tipos e à dupla ênfase da programação funcional nas funções, as linguagens de programação funcional têm uma afinidade natural pelos tipos de união e oferecem estruturas sintáticas para facilitar seu uso.

AProgrammer
fonte
7

As técnicas de programação imperativa, como frequentemente usadas no OO, dependem frequentemente de dois padrões:

  1. Ter sucesso ou lançar exceção,
  2. Retorne nullpara indicar "sem valor" ou falha.

O paradigma funcional normalmente evita ambos, preferindo retornar um tipo composto que indica motivo de sucesso / falha ou valor / nenhum valor.

As uniões discriminadas se ajustam à conta desses tipos de compostos. Por exemplo, na primeira instância, você pode retornar trueou alguma estrutura de dados que descreve a falha. No segundo caso, uma união que contém um valor, ou none, niletc. O segundo caso é tão comum, que muitas linguagens funcionais têm um "talvez" ou do tipo "opção" built-in para representar esse valor / nenhum sindicato.

Ao mudar para um estilo funcional com, por exemplo, C #, você encontrará rapidamente a necessidade desses tipos de compostos. void/throwe nullsimplesmente não se sente bem com esse código. E os sindicatos discriminados (DUs) se encaixam bem. Assim, você se viu querendo eles, assim como muitos de nós.

A boa notícia é que existem muitas bibliotecas por aí que modelam DUs em, por exemplo, C # (dê uma olhada na minha própria biblioteca Succinc <T> , por exemplo).

David Arno
fonte
2

Geralmente, os tipos de soma seriam menos úteis nas linguagens OO convencionais, pois estão resolvendo um tipo de problema semelhante ao da subtipo OO. Uma maneira de olhar para eles é que ambos lidam com subtipagem, mas OO é, openou seja, é possível adicionar subtipos arbitrários a um tipo pai e tipos de soma são, closedou seja, é possível determinar antecipadamente quais subtipos são válidos.

Agora, muitas linguagens OO combinam subtipagem com outros conceitos, como estruturas herdadas, polimorfismo, digitação de referência etc. para torná-las geralmente mais úteis. A consequência é que eles tendem a ser mais trabalho para configurar (com classes e construtores e outros enfeites) para que tendiam a não ser usados para coisas como Results e Options e assim sucessivamente até digitação genérico tornou-se comum.

Eu diria também que o foco nas relações do mundo real que a maioria das pessoas aprendeu quando começou a programação OO, como Dog isa Animal, significava que Inteiro é um resultado ou Erro é um resultado um pouco estranho. Embora as idéias sejam bastante semelhantes.

Quanto ao motivo pelo qual as linguagens funcionais podem preferir a digitação fechada à aberta, um possível motivo é que elas tendem a preferir a correspondência de padrões. Isso é útil para o polimorfismo de função, mas também funciona muito bem com tipos fechados, pois o compilador pode verificar estaticamente se a correspondência está cobrindo todos os subtipos. Isso pode fazer com que o idioma pareça mais consistente, embora eu não acredite que exista algum benefício inerente (eu poderia estar enganado).

Alex
fonte
Entre: Scala fechou herança. sealedsignifica "só pode ser estendido na mesma unidade de compilação", o que permite fechar o conjunto de subclasses em tempo de design.
Jörg W Mittag
-3

Felizmente, Swift usa sindicatos discretos, exceto que os chama de "enums". As enums são uma das cinco categorias fundamentais de objetos no Swift, que são classe, estrutura, enumeração, tupla e fechamento. Os opcionais Swift são enumerações que são uniões discriminatórias e são absolutamente essenciais para qualquer código Swift.

Portanto, a premissa de que "os sindicatos discriminadores estão associados à programação funcional" está errada.

gnasher729
fonte