Eu estive analisando o F # recentemente e, embora não seja provável que pule a barreira tão cedo, ele definitivamente destaca algumas áreas em que o C # (ou o suporte de biblioteca) poderia facilitar a vida.
Em particular, estou pensando no recurso de correspondência de padrões do F #, que permite uma sintaxe muito rica - muito mais expressiva do que os atuais comutadores / equivalentes em C # condicionais. Não tentarei dar um exemplo direto (meu F # não depende dele), mas, em suma, ele permite:
- corresponder por tipo (com verificação de cobertura total para uniões discriminadas) [observe que isso também infere o tipo da variável vinculada, fornecendo acesso a membros etc.]
- combinar por predicado
- combinações dos itens acima (e possivelmente de outros cenários dos quais não conheço)
Embora seja adorável o C # eventualmente emprestar [ahem] parte dessa riqueza, nesse meio tempo, eu estive analisando o que pode ser feito em tempo de execução - por exemplo, é bastante fácil reunir alguns objetos para permitir:
var getRentPrice = new Switch<Vehicle, int>()
.Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
.Case<Bicycle>(30) // returns a constant
.Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
.Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
.ElseThrow(); // or could use a Default(...) terminator
onde getRentPrice é um Func <Vehicle, int>.
[nota - talvez Switch / Case tenha os termos errados ... mas mostra a ideia]
Para mim, isso é muito mais claro que o equivalente, usando if / else repetido ou uma condicional ternária composta (que fica muito confusa para expressões não triviais - colchetes em abundância). Ele também evita muitas transmissões e permite uma extensão simples (diretamente ou por meio de métodos de extensão) para correspondências mais específicas, por exemplo, uma correspondência InRange (...) comparável à VB Select ... Case "x To y "uso.
Estou apenas tentando avaliar se as pessoas pensam que há muitos benefícios em construções como as mencionadas acima (na ausência de suporte ao idioma)?
Observe também que eu tenho jogado com 3 variantes do acima:
- uma versão Func <TSource, TValue> para avaliação - comparável a declarações condicionais ternárias compostas
- uma versão Action <TSource> - comparável a if / else if / else if / else if / else
- uma versão Expression <Func <TSource, TValue >> - como a primeira, mas utilizável por provedores arbitrários de LINQ
Além disso, o uso da versão baseada em expressão permite reescrever a árvore de expressões, essencialmente incorporando todas as ramificações em uma única expressão condicional composta, em vez de usar a chamada repetida. Não verifiquei recentemente, mas em algumas versões anteriores do Entity Framework, lembro que isso era necessário, pois não gostava muito de InvocationExpression. Ele também permite um uso mais eficiente com o LINQ-to-Objects, uma vez que evita repetidas invocações de delegados - os testes mostram uma correspondência como a acima (usando o formulário Expressão) com a mesma velocidade [marginalmente mais rápida, na verdade] em comparação com o equivalente em C # declaração condicional composta. Para ser completo, a versão baseada em Func demorou quatro vezes mais que a instrução condicional C #, mas ainda é muito rápida e dificilmente será um grande gargalo na maioria dos casos de uso.
Congratulo-me com quaisquer pensamentos / sugestões / críticas / etc sobre o que foi dito acima (ou sobre as possibilidades de um suporte mais avançado à linguagem C # ... aqui está a esperança ;-p).
fonte
switch-case
declaração. Não me interpretem mal, acho que tem o seu lugar e provavelmente procurarei uma maneira de implementar.Respostas:
Eu sei que é um tópico antigo, mas no c # 7 você pode fazer:
fonte
Depois de tentar fazer essas coisas "funcionais" em C # (e até tentar um livro), cheguei à conclusão de que não, com algumas exceções, essas coisas não ajudam muito.
O principal motivo é que idiomas como o F # obtêm muito de seu poder ao realmente oferecer suporte a esses recursos. Não "você pode fazer isso", mas "é simples, é claro, é esperado".
Por exemplo, na correspondência de padrões, o compilador informa se há uma correspondência incompleta ou quando outra correspondência nunca será atingida. Isso é menos útil com tipos abertos, mas ao combinar uma união ou tupla discriminada, é muito bacana. No F #, você espera que as pessoas correspondam aos padrões e isso faz sentido instantaneamente.
O "problema" é que, depois que você começa a usar alguns conceitos funcionais, é natural querer continuar. No entanto, alavancar tuplas, funções, aplicação de método parcial e currying, correspondência de padrões, funções aninhadas, genéricos, suporte a mônada etc. no C # fica muito feio, muito rapidamente. É divertido, e algumas pessoas muito inteligentes fizeram coisas muito legais em C #, mas na verdade usá- lo parece pesado.
O que acabei usando frequentemente (em todos os projetos) em C #:
** Mas observe: a falta de generalização automática e inferência de tipo realmente atrapalha o uso desses recursos. **
Tudo isso dito, como outra pessoa mencionou, em uma equipe pequena, com um objetivo específico, sim, talvez eles possam ajudar se você estiver preso ao C #. Mas, na minha experiência, eles geralmente pareciam mais problemas do que valiam - YMMV.
Alguns outros links:
fonte
Indiscutivelmente, o motivo pelo qual o C # não simplifica a ativação de tipos é porque é principalmente uma linguagem orientada a objetos, e a maneira 'correta' de fazer isso em termos orientados a objetos seria definir um método GetRentPrice em Vehicle and substituí-lo em classes derivadas.
Dito isso, passei um pouco de tempo brincando com linguagens multiparadigma e funcionais, como F # e Haskell, que têm esse tipo de capacidade, e me deparei com vários lugares em que isso seria útil antes (por exemplo, quando você não estão escrevendo os tipos que você precisa ativar, para que você não possa implementar um método virtual neles) e é algo que eu gostaria de receber no idioma junto com uniões discriminadas.
[Editar: Parte removida sobre desempenho, como Marc indicou que poderia estar em curto-circuito]
Outro problema em potencial é o de usabilidade - fica claro na chamada final o que acontece se a partida não atender a alguma condição, mas qual é o comportamento se ela corresponder a duas ou mais condições? Deveria lançar uma exceção? Deve retornar a primeira ou a última partida?
Uma maneira que costumo usar para resolver esse tipo de problema é usar um campo de dicionário com o tipo como chave e o lambda como valor, que é bastante conciso para construir usando a sintaxe do inicializador de objetos; no entanto, isso explica apenas o tipo concreto e não permite predicados adicionais; portanto, pode não ser adequado para casos mais complexos. [Observação: se você observar a saída do compilador C #, ele freqüentemente converterá instruções de chave em tabelas de salto baseadas em dicionário; portanto, não parece haver um bom motivo para não suportar a ativação de tipos]
fonte
Eu não acho que esses tipos de bibliotecas (que funcionam como extensões de idioma) provavelmente recebam ampla aceitação, mas são divertidos de brincar e podem ser realmente úteis para equipes pequenas que trabalham em domínios específicos onde isso é útil. Por exemplo, se você estiver escrevendo toneladas de 'regras / lógica de negócios' que fazem testes de tipo arbitrário como este e outros enfeites, posso ver como seria útil.
Não faço idéia se é provável que esse seja um recurso da linguagem C # (parece duvidoso, mas quem pode ver o futuro?).
Para referência, o F # correspondente é aproximadamente:
assumindo que você definiu uma hierarquia de classes ao longo das linhas de
fonte
Para responder sua pergunta, sim, acho que as construções sintáticas de correspondência de padrões são úteis. Eu, pelo menos, gostaria de ver suporte sintático em C # para ele.
Aqui está minha implementação de uma classe que fornece (quase) a mesma sintaxe que você descreve
Aqui está um código de teste:
fonte
Correspondência de padrões (conforme descrito aqui ), seu objetivo é desconstruir valores de acordo com a especificação de tipo. No entanto, o conceito de uma classe (ou tipo) em C # não concorda com você.
Não há nada de errado com o design de linguagem de múltiplos paradigmas; pelo contrário, é muito bom ter lambdas em C #, e Haskell pode fazer coisas imperativas como, por exemplo, E / S. Mas não é uma solução muito elegante, não da maneira Haskell.
Porém, como as linguagens de programação procedural sequencial podem ser entendidas em termos de cálculo lambda, e o C # se encaixa bem nos parâmetros de uma linguagem processual sequencial, é uma boa opção. Mas, pegar algo do contexto funcional puro de dizer Haskell e colocar esse recurso em uma linguagem que não é pura, bem, fazer exatamente isso, não garantirá um resultado melhor.
O que quero dizer é que o que faz com que a correspondência de padrões esteja ligada ao design da linguagem e ao modelo de dados. Dito isso, não acredito que a correspondência de padrões seja um recurso útil do C # porque ele não resolve problemas típicos de C # nem se encaixa bem no paradigma de programação imperativa.
fonte
IMHO a maneira OO de fazer essas coisas é o padrão Visitor. Os métodos de membros visitantes simplesmente agem como construções de maiúsculas e minúsculas e você deixa o próprio idioma manipular o envio apropriado sem precisar "espiar" os tipos.
fonte
Embora não seja muito "C-sharpey" ativar o tipo, eu sei que o construto seria bastante útil no uso geral - eu tenho pelo menos um projeto pessoal que poderia usá-lo (embora seja um ATM gerenciável). Existe muito problema de desempenho de compilação, com a reescrita da árvore de expressões?
fonte
Eu acho que isso parece realmente interessante (+1), mas uma coisa a ter cuidado: o compilador C # é muito bom em otimizar as declarações de opção. Não apenas para curtos-circuitos - você obtém uma IL completamente diferente, dependendo de quantos casos você tem e assim por diante.
Seu exemplo específico faz algo que eu consideraria muito útil - não há sintaxe equivalente a caso por tipo, como (por exemplo)
typeof(Motorcycle)
não é uma constante.Isso fica mais interessante em aplicativos dinâmicos - sua lógica aqui pode ser facilmente orientada por dados, fornecendo execução no estilo 'mecanismo de regras'.
fonte
Você pode conseguir o que está procurando usando uma biblioteca que escrevi, chamada OneOf
A principal vantagem sobre
switch
(eif
eexceptions as control flow
) é que é seguro em tempo de compilação - não há manipulador padrão ou falhaEstá em Nuget e tem como alvo net451 e netstandard1.6
fonte