ventajas programación programacion orientada online objetos lenguaje funcional ejemplos curso haskell f# functional-programming

haskell - programacion - programación funcional vs orientada a objetos



En Programación funcional, ¿se considera una mala práctica tener coincidencias de patrones incompletas? (7)

Creo que depende bastante del contexto. ¿Estás tratando de escribir código robusto y fácil de depurar, o estás tratando de escribir algo simple y sucinto?

Si estuviera trabajando en un proyecto a largo plazo con múltiples desarrolladores, pondría la afirmación para dar un mensaje de error más útil. También estoy de acuerdo con el comentario de Pascal de que no usar un comodín sería ideal desde una perspectiva de ingeniería de software.

Si estuviera trabajando en un proyecto de menor escala en el que yo era el único desarrollador, no lo pensaría dos veces antes de utilizar una coincidencia incompleta. Si es necesario, siempre puedes verificar las advertencias del compilador.

Creo que también depende un poco de los tipos con los que te estás enfrentando. De forma realista, no se agregarán casos de unión adicionales al tipo de lista, por lo que no tendrá que preocuparse por la correspondencia frágil. Por otro lado, en el código que usted controla y en el que está trabajando activamente, es posible que haya tipos que están en constante cambio y que se agreguen cajas de unión adicionales, lo que significa que puede valer la pena protegerse contra la compatibilidad frágil.

En general, se considera una mala práctica utilizar machihembrados no exhaustivos en lenguajes funcionales como Haskell o F #, lo que significa que los casos especificados no cubren todos los casos de entrada posibles.

En particular, ¿debería permitir que el código falle con una MatchFailureException etc. o debería siempre cubrir todos los casos y lanzar un error explícitamente si es necesario?

Ejemplo:

let head (x::xs) = x

O

let head list = match list with | x::xs -> x | _ -> failwith "Applying head to an empty list"

F # (a diferencia de Haskell) da una advertencia para el primer código, ya que el [] -case no está cubierto, pero ¿puedo ignorarlo sin romper las convenciones de estilo funcional en aras de la concisión? Un MatchFailure sí plantea el problema bastante bien después de todo ...


El preludio de Haskell (funciones estándar) contiene muchas funciones parciales, por ejemplo, la cabeza y la cola solo funcionan en listas no vacías, pero no me pregunten por qué.


Esta pregunta tiene dos aspectos.

  1. Para el usuario de la API , el error ... simplemente arroja una excepción System.Exception, que no es específica (y, por lo tanto, a veces se considera una mala práctica en sí misma). Por otro lado, la MatchFailureException lanzada implícitamente se puede atrapar específicamente utilizando un patrón de prueba de tipo y, por lo tanto, es preferible.
  2. Para el revisor del código de implementación , falla ... documenta claramente que el implementador al menos ha reflexionado sobre los posibles casos, y por lo tanto es preferible.

Como los dos aspectos se contradicen entre sí, la respuesta correcta depende de las circunstancias (ver también la respuesta de kvb). Una solución que es 100% "correcta" desde cualquier punto de vista tendría que ser

  • lidiar con cada caso de forma explícita,
  • lanzar una excepción específica cuando sea necesario, y
  • documentar claramente la excepción

Ejemplo:

/// <summary>Gets the first element of the list.</summary> /// <exception cref="ArgumentException">The list is empty.</exception> let head list = match list with | [] -> invalidArg "list" "The list is empty." | x::xs -> x


Este es un caso especial de una pregunta más general, que es "si alguna vez creas funciones parciales". Las coincidencias de patrones incompletas son solo un ejemplo de funciones parciales.

Como regla, las funciones totales son preferibles. Cuando se encuentre observando una función que solo tiene que ser parcial, pregúntese primero si puede resolver el problema en el sistema de tipos. A veces eso es más problemas que su valor (por ejemplo, crear un tipo completo de listas con longitudes conocidas solo para evitar el problema de "cabeza []"). Entonces es una compensación.

O tal vez solo pregunta si es una buena práctica en funciones parciales decir cosas como

head [] = error "head: empty list"

En cuyo caso, la respuesta es ¡SÍ!


Explícito es mejor que implícito (tomado del Zen de Python;))

Es exactamente lo mismo que en C cambiar de una enum ... Es mejor escribir todos los casos (con una caída) en lugar de simplemente poner un default , porque el compilador le dirá si agrega elementos nuevos a la enumeración y olvidaste manejarlos


Las maquinaciones de patrones no exhaustivas son idiomáticas en Haskell pero extremadamente mal estilo en F # (y OCaml, ML estándar, etc.).

La comprobación de exhaustividad es una forma muy valiosa de detectar errores en tiempo de compilación en lenguajes estrictos como F #, pero los lenguajes perezosos como Haskell a menudo usan listas perezosas infinitas donde la lista vacía no puede surgir, por lo que (históricamente) eligieron la brevedad sobre la corrección estáticamente comprobada.


Si completa sus coincidencias de patrones con un constructor [] y no con el catch-all _ , el compilador tendrá la oportunidad de decirle que vuelva a mirar la función con una advertencia el día en que alguien agregue un tercer constructor a las listas.

Mis colegas y yo, trabajando en un gran proyecto OCaml (más de 200,000 líneas), nos obligamos a evitar advertencias parciales de coincidencia de patrones (incluso si eso significa escribir | ... -> assert false de vez en cuando) y evitar así- llamados " patrones de emparejamiento frágiles " (emparejamientos de patrones escritos de tal manera que la adición de un constructor puede no detectarse) también. Consideramos que los beneficios de mantenibilidad.