Parece que el código F # a menudo coincide con patrones contra tipos. Ciertamente
match opt with
| Some val -> Something(val)
| None -> Different()
Parece común.
Pero desde una perspectiva OOP, eso se parece mucho al flujo de control basado en una verificación de tipo de tiempo de ejecución, que generalmente estaría mal visto. Para explicarlo, en OOP probablemente preferirías usar la sobrecarga:
type T =
abstract member Route : unit -> unit
type Foo() =
interface T with
member this.Route() = printfn "Go left"
type Bar() =
interface T with
member this.Route() = printfn "Go right"
Este es ciertamente más código. OTOH, me parece que mi OOP-y tiene ventajas estructurales:
- la extensión a una nueva forma de
T
es fácil; - No tengo que preocuparme por encontrar la duplicación del flujo de control de elección de ruta; y
- la elección de la ruta es inmutable en el sentido de que una vez que tengo una
Foo
en la mano, nunca necesito preocuparme porBar.Route()
la implementación
¿Existen ventajas para la coincidencia de patrones con los tipos que no veo? ¿Se considera idiomático o es una capacidad que no se usa comúnmente?
object-oriented
functional-programming
f#
pattern-matching
Larry OBrien
fuente
fuente
But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.
suena demasiado dogmático. A veces, desea separar sus operaciones de su jerarquía: quizás 1) no puede agregar una operación a una jerarquía b / c que no posee la jerarquía; 2) las clases que desea tener la operación no coinciden con su jerarquía; 3) puede agregar la operación a su jerarquía, pero no quiere b / c no quiere saturar la API de su jerarquía con un montón de basura que la mayoría de los clientes no usan.Some
yNone
no son tipos. Ambos son constructores cuyos tipos sonforall a. a -> option a
yforall a. option a
(perdón, no estoy seguro de cuál es la sintaxis para las anotaciones de tipo en F #).Respuestas:
Tiene razón en que las jerarquías de clases OOP están muy relacionadas con las uniones discriminadas en F # y que la coincidencia de patrones está muy relacionada con las pruebas de tipo dinámico. De hecho, así es como F # compila uniones discriminadas a .NET.
En cuanto a la extensibilidad, hay dos lados del problema:
Dicho esto, F # le dará una advertencia cuando omita un caso en la coincidencia de patrones, por lo que agregar nuevos casos sindicales en realidad no es tan malo.
Con respecto a la búsqueda de duplicaciones en la elección de la raíz: F # le dará una advertencia cuando tenga una coincidencia duplicada, por ejemplo:
El hecho de que "la elección de ruta es inmutable" también podría ser problemático. Por ejemplo, si desea compartir la implementación de una función entre
Foo
yBar
casos, pero hacer algo más para elZoo
caso, puede codificarlo fácilmente utilizando la coincidencia de patrones:En general, FP está más enfocado en diseñar primero los tipos y luego agregar funciones. Por lo tanto, realmente se beneficia del hecho de que puede ajustar sus tipos (modelo de dominio) en un par de líneas en un solo archivo y luego agregar fácilmente las funciones que operan en el modelo de dominio.
Los dos enfoques: OO y FP son bastante complementarios y ambos tienen ventajas y desventajas. Lo complicado (desde la perspectiva OO) es que F # generalmente usa el estilo FP como predeterminado. Pero si realmente hay más necesidad de agregar nuevas subclases, siempre puede usar interfaces. Pero en la mayoría de los sistemas, igualmente necesita agregar tipos y funciones, por lo que la elección realmente no importa tanto, y usar uniones discriminadas en F # es mejor.
Recomiendo esta gran serie de blogs para más información.
fuente
Ha observado correctamente que la coincidencia de patrones (esencialmente una declaración de interruptor sobrealimentado) y el despacho dinámico tienen similitudes. También coexisten en algunos idiomas, con un resultado muy agradable. Sin embargo, hay ligeras diferencias.
Podría usar el sistema de tipos para definir un tipo que solo puede tener un número fijo de subtipos:
Habrá que nunca ser otro subtipo de
Bool
oOption
, por lo subclases no parece ser útiles (algunos idiomas como el Scala tienen una noción de la subclasificación que puede manejar esto - una clase puede ser marcado como fuera de “final” de la unidad de compilación actual, pero subtipos se puede definir dentro de esta unidad de compilación).Debido a que los subtipos de un tipo como
Option
ahora se conocen estáticamente , el compilador puede advertir si olvidamos manejar un caso en nuestra coincidencia de patrones. Esto significa que una coincidencia de patrones es más como un downcast especial que nos obliga a manejar todas las opciones.Además, el despacho de método dinámico (que se requiere para OOP) también implica una verificación del tipo de tiempo de ejecución, pero de un tipo diferente. Por lo tanto, es bastante irrelevante si hacemos este tipo de verificación explícitamente a través de una coincidencia de patrones o implícitamente a través de una llamada a un método.
fuente
La coincidencia de patrones F # generalmente se realiza con una unión discriminada en lugar de con clases (y, por lo tanto, técnicamente no es una verificación de tipo) Esto permite que el compilador le avise cuando no haya encontrado casos en una coincidencia de patrones.
Otra cosa a tener en cuenta es que, en un estilo funcional, organiza las cosas por funcionalidad en lugar de por datos, por lo que las coincidencias de patrones le permiten reunir las diferentes funcionalidades en un solo lugar en lugar de estar dispersas entre las clases. Esto también tiene la ventaja de que puede ver cómo se manejan otros casos justo al lado de donde necesita hacer sus cambios.
Agregar una nueva opción se ve así:
fuente
Parcialmente, lo ve más a menudo en la programación funcional porque usa tipos para tomar decisiones con más frecuencia. Me doy cuenta de que probablemente hayas elegido ejemplos más o menos al azar, pero el OOP equivalente a tu ejemplo de coincidencia de patrones se vería más a menudo:
En otras palabras, es relativamente raro usar polimorfismo para evitar cosas rutinarias como cheques nulos en OOP. Al igual que un programador OO no crea un objeto nulo en cada pequeña situación, un programador funcional no siempre sobrecarga una función, especialmente cuando sabe que su lista de patrones está garantizada como exhaustiva. Si usa el sistema de tipos en más situaciones, verá que se usa de formas a las que no está acostumbrado.
Por el contrario, la programación idiomática equivalente funcional a su ejemplo POO lo más probable es que no utilice la coincidencia de patrones, pero tendría
fooRoute
ybarRoute
funciones que van pasando como argumentos al código de llamada. Si alguien usó la coincidencia de patrones en esa situación, generalmente se consideraría incorrecto, al igual que alguien que cambia los tipos se consideraría incorrecto en OOP.Entonces, ¿cuándo se considera que la coincidencia de patrones es un buen código de programación funcional? Cuando está haciendo más que solo mirar los tipos, y al extender los requisitos no requerirá agregar más casos. Por ejemplo,
Some val
no solo comprueba queopt
tiene tipoSome
, sino que también se uneval
al tipo subyacente para un uso con tipo seguro en el otro lado del->
. Sabes que lo más probable es que nunca necesites un tercer caso, por lo que es un buen uso.La coincidencia de patrones puede parecerse superficialmente a una declaración de cambio orientada a objetos, pero están sucediendo muchas más cosas, especialmente con patrones más largos o anidados. Asegúrese de tener en cuenta todo lo que está haciendo antes de declararlo equivalente a un código OOP mal diseñado. A menudo, está manejando sucintamente una situación que no puede representarse limpiamente en una jerarquía de herencia.
fuente
Some
yNone
no son tipos, por lo que no coinciden los patrones en los tipos. Puede emparejar patrones en constructores del mismo tipo . Esto no es como preguntar "instanceof".