Aviso justo, eu sou novo em programação funcional, por isso posso ter muitas suposições ruins.
Eu tenho aprendido sobre tipos algébricos. Muitas linguagens funcionais parecem tê-las e são bastante úteis em conjunto com a correspondência de padrões. No entanto, que problema eles realmente resolvem? Eu posso implementar um tipo algébrico aparentemente (mais ou menos) em C # assim:
public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
public T Value { get; set; }
}
var result = GetSomeValue();
if(result is None)
{
}
else
{
}
Mas acho que a maioria concorda que isso é uma bastardização da programação orientada a objetos, e você nunca deve fazê-lo. Então, a programação funcional apenas adiciona uma sintaxe mais limpa que faz com que esse estilo de programação pareça menos grosseiro? O que mais estou perdendo?
functional-programming
algebraic-data-type
ConditionRacer
fonte
fonte
class ThirdOption : Option{}
e dou a vocênew ThirdOption()
onde você esperavaSome
ouNone
?data Maybe a = Just a | Nothing
(equivalente adata Option a = Some a | None
no seu exemplo): você não pode adicionar um terceiro caso post-hoc. Embora você possa emular tipos de soma em C # da maneira que você mostrou, não é a mais bonita.Respostas:
Classes com interfaces e herança apresentam um mundo aberto: qualquer pessoa pode adicionar um novo tipo de dados. Para uma determinada interface, pode haver classes implementando-a em todo o mundo, em diferentes arquivos, em diferentes projetos, em diferentes empresas. Eles facilitam a adição de casos às estruturas de dados, mas como as implementações da interface são descentralizadas, é difícil adicionar um novo método à interface. Uma vez que uma interface é pública, ela fica basicamente congelada. Ninguém conhece todas as implementações possíveis.
Os tipos de dados algébricos são duplos, estão fechados . Todos os casos dos dados estão listados em um único local e as operações não apenas podem listar exaustivamente as variantes, mas são incentivadas a fazê-lo. Consequentemente, escrever uma nova função operando em um tipo de dados algébrico é trivial: basta escrever a função maldita. Em troca, adicionar novos casos é complicado porque você precisa revisar basicamente toda a base de código e estender cada
match
. Semelhante à situação com interfaces, na biblioteca padrão do Rust, a adição de uma nova variante é uma mudança de última hora (para tipos públicos).Estes são os dois lados do problema de expressão . Os tipos de dados algébricos são uma solução incompleta para eles, mas o OOP também. Ambos têm vantagens, dependendo de quantos casos de dados existem, com que frequência esses casos mudam e com que frequência as operações são estendidas ou alteradas. (É por isso que muitas linguagens modernas fornecem ambos, ou algo semelhante, ou vão direto para mecanismos mais poderosos e mais complicados que tentam incluir as duas abordagens.)
fonte
Talvez seja uma simplificação, mas sim.
Sejamos claros sobre o que são tipos de dados algébricos (resumindo este link fino de Aprenda você como Haskell):
seu exemplo realmente funciona apenas com o primeiro.
O que talvez você esteja perdendo é que, ao fornecer essas duas operações básicas, as linguagens funcionais permitem criar todo o resto. O C # possui estruturas, classes, enumerações, genéricos e pilhas de regras para governar como essas coisas se comportam.
Combinadas com alguma sintaxe para ajudar, as linguagens funcionais podem decompor as operações nesses dois caminhos, fornecendo uma abordagem limpa, simples e elegante dos tipos.
Eles resolvem o mesmo problema que qualquer outro sistema de tipos: "que valores são legais para usar aqui?" - eles apenas adotam uma abordagem diferente.
fonte
Pode ser uma surpresa saber que a correspondência de padrões não é considerada a maneira mais idiomática de trabalhar com Opções. Veja a documentação das Opções do Scala para mais informações. Não sei por que tantos tutoriais de FP incentivam esse uso.
Principalmente o que está faltando é que existem várias funções criadas para facilitar o trabalho com o Options. Considere o exemplo principal dos documentos do Scala:
Observe como
map
efilter
permita que você encadeie operações nas Opções, sem precisar verificar em cada ponto se você temNone
ou não. Então, no final, você usagetOrElse
para especificar um valor padrão. Em nenhum momento você está fazendo algo "nojento", como verificar tipos. Qualquer verificação de tipo inevitável é feita internamente na biblioteca. Haskell tem seu próprio conjunto de funções análogas, sem mencionar o grande conjunto de funções que funcionará em qualquer mônada ou functor.Outros tipos de dados algébricos têm suas próprias maneiras idiomáticas de trabalhar com eles, e a correspondência de padrões é baixa no totem na maioria dos casos. Da mesma forma, ao criar seus próprios tipos, espera-se que você forneça funções semelhantes para trabalhar com eles.
fonte