Estou lendo sobre programação funcional e notei que a correspondência de padrões é mencionada em muitos artigos como um dos principais recursos das linguagens funcionais.
Alguém pode explicar para um desenvolvedor Java / C ++ / JavaScript o que isso significa?
Respostas:
Para entender a correspondência de padrões, é necessário explicar três partes:
Tipos de dados algébricos em poucas palavras
As linguagens funcionais do tipo ML permitem definir tipos de dados simples chamados "uniões disjuntas" ou "tipos de dados algébricos". Essas estruturas de dados são contêineres simples e podem ser definidas recursivamente. Por exemplo:
define uma estrutura de dados do tipo pilha. Pense nisso como equivalente a este C #:
Portanto, os identificadores
Cons
eNil
definem uma classe simples e simples, ondeof x * y * z * ...
define um construtor e alguns tipos de dados. Os parâmetros para o construtor não têm nome, são identificados por posição e tipo de dados.Você cria instâncias da sua
a list
classe como tal:Qual é o mesmo que:
Correspondência de padrões em poucas palavras
A correspondência de padrões é um tipo de teste de tipo. Então, digamos que criamos um objeto de pilha como o descrito acima, podemos implementar métodos para espiar e exibir a pilha da seguinte maneira:
Os métodos acima são equivalentes (embora não implementados como tais) aos seguintes C #:
(Quase sempre, as linguagens ML implementam a correspondência de padrões sem testes de tipo ou conversão em tempo de execução; portanto, o código C # é um tanto enganador. Vamos deixar de lado os detalhes da implementação com alguns acenos de mão :))
Decomposição da estrutura de dados em poucas palavras
Ok, vamos voltar ao método peek:
O truque é entender que os identificadores
hd
etl
são variáveis (errm ... já que são imutáveis, na verdade não são "variáveis", mas "valores";)). Ses
tiver o tipoCons
, retiraremos seus valores do construtor e os vincularemos às variáveis nomeadashd
etl
.A correspondência de padrões é útil porque nos permite decompor uma estrutura de dados por sua forma, em vez de seu conteúdo . Imagine se definirmos uma árvore binária da seguinte maneira:
Podemos definir algumas rotações em árvore da seguinte maneira:
(O
let rotateRight = function
construtor é açúcar de sintaxe paralet rotateRight s = match s with ...
.)Portanto, além de vincular a estrutura de dados às variáveis, também podemos fazer uma busca detalhada. Digamos que temos um nó
let x = Node(Nil, 1, Nil)
. Se chamarmosrotateLeft x
, testamosx
o primeiro padrão, que falha ao corresponder porque o filho certo tem o tipo emNil
vez deNode
. Elex -> x
passará para o próximo padrão, que corresponderá a qualquer entrada e a retornará sem modificação.Para comparação, escreveríamos os métodos acima em C # como:
Para serio.
A correspondência de padrões é incrível
Você pode implementar algo semelhante à correspondência de padrões em C # usando o padrão de visitante , mas não é tão flexível porque você não pode decompor efetivamente estruturas de dados complexas. Além disso, se você estiver usando correspondência de padrões, o compilador informará se você deixou um caso de fora . Quão incrível é isso?
Pense em como você implementaria funcionalidades semelhantes em C # ou idiomas sem correspondência de padrões. Pense em como você faria isso sem testes de teste e transmissões em tempo de execução. Certamente não é difícil , apenas pesado e volumoso. E você não tem a verificação do compilador para garantir que cobriu todos os casos.
Portanto, a correspondência de padrões ajuda a decompor e navegar nas estruturas de dados em uma sintaxe muito conveniente e compacta, permitindo que o compilador verifique a lógica do seu código, pelo menos um pouco. Realmente é uma característica do assassino.
fonte
Resposta curta: a correspondência de padrões surge porque as linguagens funcionais tratam o sinal de igual como uma asserção de equivalência em vez de atribuição.
Resposta longa: a correspondência de padrões é uma forma de envio com base na "forma" do valor que é fornecido. Em uma linguagem funcional, os tipos de dados que você define são geralmente conhecidos como uniões discriminadas ou tipos de dados algébricos. Por exemplo, o que é uma lista (vinculada)? Uma lista vinculada
List
de itens de algum tipoa
é a lista vaziaNil
ou algum elemento do tipoa
Cons
ed em umaList a
(uma lista dea
s). Em Haskell (a linguagem funcional com a qual estou mais familiarizado), escrevemos issoTodas as uniões discriminadas são definidas desta maneira: um único tipo possui um número fixo de maneiras diferentes de criá-lo; os criadores, como
Nil
eCons
aqui, são chamados de construtores. Isso significa que um valor do tipoList a
pode ter sido criado com dois construtores diferentes - ele pode ter duas formas diferentes. Então, suponha que desejemos escrever umahead
função para obter o primeiro elemento da lista. Em Haskell, escreveríamos isso comoComo os
List a
valores podem ser de dois tipos diferentes, precisamos lidar com cada um separadamente; esta é a correspondência de padrões. Emhead x
, sex
corresponder ao padrãoNil
, executamos o primeiro caso; se corresponder ao padrãoCons h _
, executamos o segundo.Resposta curta, explicada: acho que uma das melhores maneiras de pensar sobre esse comportamento é alterando a maneira como você pensa no sinal de igual. Nas línguas entre parênteses, em geral,
=
denota atribuição:a = b
significa "transformara
emb
". Em muitas linguagens funcionais, contudo,=
denota uma afirmação de igualdade:let Cons a (Cons b Nil) = frob x
afirma que a coisa à esquerdaCons a (Cons b Nil)
, é equivalente à coisa à direitafrob x
; além disso, todas as variáveis usadas à esquerda se tornam visíveis. Também é o que está acontecendo com os argumentos das funções: afirmamos que o primeiro argumento se pareceNil
e, se não, continuamos verificando.fonte
Cons
significa isso ?Cons
é a cons tructor que cria uma lista (ligada) para fora de uma cabeça (aa
) e uma cauda (aList a
). O nome vem do Lisp. No Haskell, para o tipo de lista interno, é o:
operador (que ainda é pronunciado "contras").Isso significa que, em vez de escrever
Você pode escrever
Ei, C ++ também suporta correspondência de padrões.
fonte
A correspondência de padrões é como métodos sobrecarregados com esteróides. O caso mais simples seria o mesmo que você viu em java, argumentos são uma lista de tipos com nomes. O método correto para chamar é baseado nos argumentos passados e funciona como uma atribuição desses argumentos ao nome do parâmetro.
Os padrões dão um passo adiante e podem desestruturar os argumentos transmitidos ainda mais. Ele também pode potencialmente usar guardas para realmente corresponder com base no valor do argumento. Para demonstrar, vou fingir que o JavaScript tinha correspondência de padrões.
Em foo2, espera que a seja uma matriz, divide o segundo argumento, esperando um objeto com dois props (prop1, prop2) e atribui os valores dessas propriedades às variáveis d e e, em seguida, espera que o terceiro argumento seja 35)
Diferentemente do JavaScript, os idiomas com correspondência de padrões geralmente permitem várias funções com o mesmo nome, mas com padrões diferentes. Dessa forma, é como uma sobrecarga de método. Vou dar um exemplo em erlang:
Borrar os olhos um pouco e você pode imaginar isso em javascript. Algo assim talvez:
Aponte que, quando você chama fibo, a implementação usada é baseada nos argumentos, mas onde o Java é limitado a tipos como o único meio de sobrecarga, a correspondência de padrões pode fazer mais.
Além da sobrecarga de funções, como mostrado aqui, o mesmo princípio pode ser aplicado em outros locais, como declarações de caso ou asserções de desestruturação. JavaScript ainda tem isso em 1.7 .
fonte
A correspondência de padrões permite combinar um valor (ou um objeto) com alguns padrões para selecionar uma ramificação do código. Do ponto de vista do C ++, pode parecer um pouco semelhante à
switch
declaração. Em linguagens funcionais, a correspondência de padrões pode ser usada para correspondência em valores primitivos padrão, como números inteiros. No entanto, é mais útil para tipos compostos.Primeiro, vamos demonstrar a correspondência de padrões em valores primitivos (usando pseudo-C ++ estendido
switch
):O segundo uso lida com tipos de dados funcionais, como tuplas (que permitem armazenar vários objetos em um único valor) e uniões discriminadas, que permitem criar um tipo que pode conter uma das várias opções. Parece um pouco,
enum
exceto que cada rótulo também pode conter alguns valores. Em uma sintaxe pseudo-C ++:Um valor do tipo
Shape
agora pode conterRectangle
todas as coordenadas ou aCircle
com o centro e o raio. A correspondência de padrões permite escrever uma função para trabalhar com oShape
tipo:Por fim, você também pode usar padrões aninhados que combinam os dois recursos. Por exemplo, você pode usar
Circle(0, 0, radius)
para corresponder a todas as formas que têm o centro no ponto [0, 0] e têm qualquer raio (o valor do raio será atribuído à nova variávelradius
).Isso pode parecer um pouco estranho do ponto de vista do C ++, mas espero que meu pseudo-C ++ deixe a explicação clara. A programação funcional é baseada em conceitos bem diferentes, por isso faz mais sentido em uma linguagem funcional!
fonte
A correspondência de padrões é onde o intérprete do seu idioma seleciona uma função específica com base na estrutura e no conteúdo dos argumentos que você fornecer.
Não é apenas um recurso de idioma funcional, mas está disponível para muitos idiomas diferentes.
A primeira vez que me deparei com a ideia foi quando aprendi prólogo, onde é realmente central para o idioma.
por exemplo
O código acima dará o último item de uma lista. O argumento de entrada é o primeiro e o resultado é o segundo.
Se houver apenas um item na lista, o intérprete selecionará a primeira versão e o segundo argumento será definido como igual ao primeiro, ou seja, um valor será atribuído ao resultado.
Se a lista tiver uma cabeça e um rabo, o intérprete escolherá a segunda versão e será recursiva até restar apenas um item na lista.
fonte
Para muitas pessoas, a escolha de um novo conceito é mais fácil se forem fornecidos alguns exemplos fáceis, então vamos lá:
Digamos que você tenha uma lista de três números inteiros e queira adicionar o primeiro e o terceiro elemento. Sem a correspondência de padrões, você poderia fazer assim (exemplos em Haskell):
Agora, embora este seja um exemplo de brinquedo, imagine que gostaríamos de vincular o primeiro e o terceiro número inteiro às variáveis e somar:
Essa extração de valores de uma estrutura de dados é o que a correspondência de padrões faz. Você basicamente "espelha" a estrutura de algo, fornecendo variáveis a serem vinculadas aos locais de interesse:
Quando você chama essa função com [1,2,3] como argumento, [1,2,3] será unificado com [primeiro
_
, terceiro], vinculando primeiro a 1, terceiro a 3 e descartando 2 (_
é um espaço reservado por coisas que você não gosta).Agora, se você deseja combinar apenas as listas com 2 como o segundo elemento, é possível fazer o seguinte:
Isso funcionará apenas para listas com 2 como seu segundo elemento e gerará uma exceção caso contrário, porque nenhuma definição para addFirstAndThird é dada para listas não correspondentes.
Até agora, usamos a correspondência de padrões apenas para a desestruturação da ligação. Acima disso, você pode fornecer várias definições da mesma função, onde a primeira definição de correspondência é usada; portanto, a correspondência de padrões é um pouco como "uma declaração de alternância nos esteroides":
addFirstAndThird adicionará felizmente o primeiro e o terceiro elemento de listas com 2 como seu segundo elemento e, caso contrário, "passará" e "retornará" 0. Essa funcionalidade "alternada" não pode ser usada apenas nas definições de funções, por exemplo:
Além disso, não está restrito a listas, mas também pode ser usado com outros tipos, por exemplo, combinando os construtores de valor Just and Nothing do tipo Maybe para "desembrulhar" o valor:
Certamente, esses eram meros exemplos de brinquedos, e eu nem tentei dar uma explicação formal ou exaustiva, mas eles deveriam bastar para entender o conceito básico.
fonte
Você deve começar com a página da Wikipedia que fornece uma boa explicação. Em seguida, leia o capítulo relevante do wikibook de Haskell .
Esta é uma boa definição do wikibook acima:
fonte
Aqui está um exemplo muito curto que mostra a utilidade de correspondência de padrões:
Digamos que você queira classificar um elemento em uma lista:
para (eu ordenei "Nova York")
em uma linguagem mais imperativa, você escreveria:
Em uma linguagem funcional, você escreveria:
Como você pode ver que a solução combinada com padrões tem menos ruído, é possível ver claramente quais são os diferentes casos e como é fácil viajar e desestruturar nossa lista.
Eu escrevi um post mais detalhado sobre isso aqui .
fonte