Estoy leyendo sobre programación funcional y he notado que la coincidencia de patrones se menciona en muchos artículos como una de las características principales de los lenguajes funcionales.
¿Alguien puede explicar para un desarrollador de Java / C ++ / JavaScript qué significa?
Respuestas:
Comprender la coincidencia de patrones requiere explicar tres partes:
Tipos de datos algebraicos en pocas palabras
Los lenguajes funcionales tipo ML le permiten definir tipos de datos simples llamados "uniones disjuntas" o "tipos de datos algebraicos". Estas estructuras de datos son contenedores simples y pueden definirse recursivamente. Por ejemplo:
define una estructura de datos tipo pila. Piense en ello como equivalente a este C #:
Entonces, los identificadores
Cons
yNil
definen una clase simple simple, dondeof x * y * z * ...
define un constructor y algunos tipos de datos. Los parámetros para el constructor no tienen nombre, se identifican por posición y tipo de datos.Crea instancias de su
a list
clase como tales:Que es lo mismo que:
Coincidencia de patrones en pocas palabras
La coincidencia de patrones es una especie de prueba de tipo. Entonces, digamos que creamos un objeto de pila como el anterior, podemos implementar métodos para mirar y reventar la pila de la siguiente manera:
Los métodos anteriores son equivalentes (aunque no se implementan como tales) al siguiente C #:
(Casi siempre, los lenguajes ML implementan la coincidencia de patrones sin pruebas de tipo o conversiones en tiempo de ejecución, por lo que el código C # es algo engañoso. Dejemos de lado los detalles de implementación con un poco de agitación manual, por favor :))
Descomposición de la estructura de datos en pocas palabras
Ok, volvamos al método de mirar:
El truco es comprender que los identificadores
hd
ytl
son variables (errm ... ya que son inmutables, no son realmente "variables", sino "valores";)). Sis
tiene el tipoCons
, entonces sacaremos sus valores del constructor y los vincularemos a las variables llamadashd
ytl
.La coincidencia de patrones es útil porque nos permite descomponer una estructura de datos por su forma en lugar de su contenido . Entonces imagine si definimos un árbol binario de la siguiente manera:
Podemos definir algunas rotaciones de árbol de la siguiente manera:
(El
let rotateRight = function
constructor es sintaxis de azúcar paralet rotateRight s = match s with ...
).Entonces, además de vincular la estructura de datos a las variables, también podemos profundizar en ella. Digamos que tenemos un nodo
let x = Node(Nil, 1, Nil)
. Si llamamosrotateLeft x
, ponemos a pruebax
contra el primer patrón, que no coincide porque el niño tiene derecho tipoNil
en lugar deNode
. Se moverá al siguiente patrón,x -> x
que coincidirá con cualquier entrada y lo devolverá sin modificaciones.A modo de comparación, escribiríamos los métodos anteriores en C # como:
Por enserio.
La coincidencia de patrones es increíble
Puede implementar algo similar a la coincidencia de patrones en C # usando el patrón de visitante , pero no es tan flexible porque no puede descomponer efectivamente estructuras de datos complejas. Además, si está utilizando coincidencia de patrones, el compilador le dirá si omitió un caso . ¿Qué tan maravilloso es eso?
Piense en cómo implementaría una funcionalidad similar en C # o lenguajes sin coincidencia de patrones. Piensa en cómo lo harías sin pruebas de prueba y lanzamientos en tiempo de ejecución. Ciertamente no es difícil , solo engorroso y voluminoso. Y no tiene que verificar el compilador para asegurarse de que ha cubierto todos los casos.
Por lo tanto, la coincidencia de patrones lo ayuda a descomponer y navegar por las estructuras de datos en una sintaxis compacta muy conveniente, le permite al compilador verificar la lógica de su código, al menos un poco. Realmente es una característica asesina.
fuente
Respuesta corta: la coincidencia de patrones surge porque los lenguajes funcionales tratan el signo igual como una afirmación de equivalencia en lugar de una asignación.
Respuesta larga: La coincidencia de patrones es una forma de envío basada en la "forma" del valor que se le da. En un lenguaje funcional, los tipos de datos que define suelen ser lo que se conoce como uniones discriminadas o tipos de datos algebraicos. Por ejemplo, ¿qué es una lista (vinculada)? Una lista vinculada
List
de cosas de algún tipoa
es la lista vacíaNil
o algún elemento de tipoa
Cons
ed en unaList a
(una lista dea
s). En Haskell (el lenguaje funcional con el que estoy más familiarizado), escribimos estoTodas las uniones discriminadas se definen de esta manera: un solo tipo tiene un número fijo de formas diferentes de crearlo; Los creadores, como
Nil
yCons
aquí, se llaman constructores. Esto significa que un valor del tipoList a
podría haberse creado con dos constructores diferentes, podría tener dos formas diferentes. Supongamos que queremos escribir unahead
función para obtener el primer elemento de la lista. En Haskell, escribiríamos esto comoComo los
List a
valores pueden ser de dos tipos diferentes, necesitamos manejar cada uno por separado; Esta es la coincidencia de patrones. Enhead x
, six
coincide con el patrónNil
, ejecutamos el primer caso; si coincide con el patrónCons h _
, ejecutamos el segundo.Respuesta corta, explicada: Creo que una de las mejores formas de pensar acerca de este comportamiento es cambiando la forma en que piensas del signo igual. En los idiomas de corchetes, en general,
=
denota asignación:a = b
significa "hacera
enb
". En muchos lenguajes funcionales, sin embargo,=
denota una afirmación de igualdad:let Cons a (Cons b Nil) = frob x
afirma que lo de la izquierdaCons a (Cons b Nil)
, es equivalente a lo de la derechafrob x
,; Además, todas las variables utilizadas a la izquierda se hacen visibles. Esto también es lo que está sucediendo con los argumentos de la función: afirmamos que se ve el primer argumentoNil
, y si no es así, seguimos comprobando.fuente
Cons
significaCons
es el cons tructor que construye una lista (vinculados) de una cabeza (laa
) y una cola (laList a
). El nombre proviene de Lisp. En Haskell, para el tipo de lista incorporada, es el:
operador (que todavía se pronuncia "contras").Significa que en lugar de escribir
Puedes escribir
Hola, C ++ también admite la coincidencia de patrones.
fuente
La coincidencia de patrones es como métodos sobrecargados en esteroides. El caso más simple sería el mismo más o menos lo mismo que viste en Java, los argumentos son una lista de tipos con nombres. El método correcto para llamar se basa en los argumentos pasados y funciona como una asignación de esos argumentos al nombre del parámetro.
Los patrones solo van un paso más allá y pueden desestructurar los argumentos pasados aún más. También puede usar guardias para que coincidan según el valor del argumento. Para demostrarlo, simularé que JavaScript tenía coincidencia de patrones.
En foo2, espera que a sea una matriz, separa el segundo argumento, esperando un objeto con dos accesorios (prop1, prop2) y asigna los valores de esas propiedades a las variables d y e, y luego espera que el tercer argumento sea 35)
A diferencia de JavaScript, los idiomas con coincidencia de patrones generalmente permiten múltiples funciones con el mismo nombre, pero patrones diferentes. De esta manera es como sobrecargar el método. Daré un ejemplo en erlang:
Desenfoca un poco tus ojos y puedes imaginar esto en javascript. Tal vez algo como esto:
Señale que cuando llama a fibo, la implementación que utiliza se basa en los argumentos, pero donde Java se limita a los tipos como el único medio de sobrecarga, la coincidencia de patrones puede hacer más.
Más allá de la sobrecarga de funciones como se muestra aquí, el mismo principio se puede aplicar en otros lugares, como declaraciones de casos o evaluaciones de desestructuración. JavaScript incluso tiene esto en 1.7 .
fuente
La coincidencia de patrones le permite hacer coincidir un valor (o un objeto) con algunos patrones para seleccionar una rama del código. Desde el punto de vista de C ++, puede sonar un poco similar a la
switch
declaración. En lenguajes funcionales, la coincidencia de patrones se puede utilizar para la coincidencia de valores primitivos estándar, como los enteros. Sin embargo, es más útil para los tipos compuestos.Primero, demostremos la coincidencia de patrones en valores primitivos (usando pseudo-C ++ extendido
switch
):El segundo uso trata con tipos de datos funcionales como las tuplas (que le permiten almacenar múltiples objetos en un solo valor) y uniones discriminadas que le permiten crear un tipo que puede contener una de varias opciones. Esto suena un poco como,
enum
excepto que cada etiqueta también puede llevar algunos valores. En una sintaxis de pseudo-C ++:Un valor de tipo
Shape
ahora puede contener ya seaRectangle
con todas las coordenadas oCircle
con el centro y el radio. La coincidencia de patrones le permite escribir una función para trabajar con elShape
tipo:Finalmente, también puede usar patrones anidados que combinen ambas características. Por ejemplo, podría usar
Circle(0, 0, radius)
para hacer coincidir todas las formas que tienen el centro en el punto [0, 0] y tienen cualquier radio (el valor del radio se asignará a la nueva variableradius
).Esto puede sonar un poco desconocido desde el punto de vista de C ++, pero espero que mi pseudo-C ++ aclare la explicación. La programación funcional se basa en conceptos bastante diferentes, por lo que tiene más sentido en un lenguaje funcional.
fuente
La coincidencia de patrones es donde el intérprete de su idioma elegirá una función particular en función de la estructura y el contenido de los argumentos que le dé.
No es solo una función de lenguaje funcional, sino que está disponible para muchos idiomas diferentes.
La primera vez que me encontré con la idea fue cuando aprendí el prólogo donde es realmente central para el idioma.
p.ej
El código anterior le dará el último elemento de una lista. El argumento de entrada es el primero y el resultado es el segundo.
Si solo hay un elemento en la lista, el intérprete elegirá la primera versión y el segundo argumento será igual al primero, es decir, se asignará un valor al resultado.
Si la lista tiene una cabeza y una cola, el intérprete elegirá la segunda versión y repetirá hasta que solo quede un elemento en la lista.
fuente
Para muchas personas, elegir un nuevo concepto es más fácil si se proporcionan algunos ejemplos fáciles, así que aquí vamos:
Digamos que tiene una lista de tres enteros y desea agregar el primer y el tercer elemento. Sin coincidencia de patrones, podría hacerlo así (ejemplos en Haskell):
Ahora, aunque este es un ejemplo de juguete, imagine que nos gustaría vincular el primer y tercer entero a las variables y sumarlas:
Esta extracción de valores de una estructura de datos es lo que hace la coincidencia de patrones. Básicamente "reflejas" la estructura de algo, dando variables para unir los lugares de interés:
Cuando llame a esta función con [1,2,3] como argumento, [1,2,3] se unificará con [first
_
,, third], enlazando primero a 1, tercero a 3 y descartando 2 (_
es un marcador de posición para cosas que no te importan).Ahora, si solo desea hacer coincidir las listas con 2 como segundo elemento, puede hacerlo así:
Esto solo funcionará para listas con 2 como su segundo elemento y, de lo contrario, arrojará una excepción, ya que no se proporciona una definición para addFirstAndThird para las listas que no coinciden.
Hasta ahora, utilizamos la coincidencia de patrones solo para desestructurar el enlace. Por encima de eso, puede dar múltiples definiciones de la misma función, donde se utiliza la primera definición de coincidencia, por lo tanto, la coincidencia de patrones es un poco como "una declaración de cambio en estereoides":
addFirstAndThird agregará felizmente el primer y el tercer elemento de las listas con 2 como su segundo elemento, y de lo contrario "caerá" y "regresará" 0. Esta funcionalidad "similar a un interruptor" no solo puede usarse en definiciones de funciones, por ejemplo:
Además, no está restringido a listas, sino que también se puede usar con otros tipos, por ejemplo, haciendo coincidir los constructores de valor Just y Nothing del tipo Maybe para "desenvolver" el valor:
Claro, esos eran simples ejemplos de juguetes, y ni siquiera intenté dar una explicación formal o exhaustiva, pero deberían ser suficientes para comprender el concepto básico.
fuente
Debes comenzar con la página de Wikipedia que da una muy buena explicación. Luego, lea el capítulo correspondiente del wikibook de Haskell .
Esta es una buena definición del wikibook anterior:
fuente
Aquí hay un ejemplo realmente corto que muestra la utilidad de coincidencia de patrones:
Supongamos que desea ordenar un elemento en una lista:
a (he ordenado "Nueva York")
en un idioma más imperativo escribirías:
En un lenguaje funcional, en su lugar, escribiría:
Como puede ver, la solución de patrón combinado tiene menos ruido, puede ver claramente cuáles son los diferentes casos y cuán fácil es viajar y desestructurar nuestra lista.
He escrito una publicación de blog más detallada al respecto aquí .
fuente