Soy consciente de que el concepto de invariantes existe en múltiples paradigmas de programación. Por ejemplo, los invariantes de bucle son relevantes en la programación OO, funcional y de procedimiento.
Sin embargo, un tipo muy útil que se encuentra en OOP es una invariante de los datos de un tipo particular. Esto es lo que llamo "invariantes basados en tipos" en el título. Por ejemplo, un Fraction
tipo podría tener un numerator
y denominator
, con la invariante de que su mcd siempre es 1 (es decir, la fracción está en una forma reducida). Solo puedo garantizar esto al tener algún tipo de encapsulación del tipo, sin dejar que sus datos se configuren libremente. A cambio, nunca tengo que verificar si se reduce, por lo que puedo simplificar algoritmos como las verificaciones de igualdad.
Por otro lado, si simplemente declaro un Fraction
tipo sin proporcionar esta garantía a través de la encapsulación, no puedo escribir con seguridad ninguna función en este tipo que suponga que la fracción se reduce, porque en el futuro alguien más podría venir y agregar una forma de conseguir una fracción no reducida.
En general, la falta de este tipo de invariante podría conducir a:
- Los algoritmos más complejos como condiciones previas deben verificarse / garantizarse en múltiples lugares
- Violaciones SECAS ya que estas precondiciones repetidas representan el mismo conocimiento subyacente (que la invariante debe ser verdadera)
- Tener que imponer condiciones previas a través de fallas de tiempo de ejecución en lugar de garantías en tiempo de compilación
Entonces mi pregunta es cuál es la respuesta de programación funcional a este tipo de invariante. ¿Existe una forma funcional-idiomática de lograr más o menos lo mismo? ¿O hay algún aspecto de la programación funcional que hace que los beneficios sean menos relevantes?
fuente
PrimeNumber
clase. Sería demasiado costoso realizar múltiples verificaciones redundantes de primalidad para cada operación, pero no es un tipo de prueba que pueda realizarse en tiempo de compilación. (Una gran cantidad de operaciones que desea realizar en números primos, por ejemplo multiplicación, no forman un cierre , es decir, los resultados probablemente no son garantizados primo (envíos como los comentarios ya que no conozco a mí mismo la programación funcional)..Respuestas:
Algunos lenguajes funcionales como OCaml tienen mecanismos incorporados para implementar tipos de datos abstractos, por lo tanto, imponen algunas invariantes . Los idiomas que no cuentan con tales mecanismos dependen de que el usuario "no mire debajo de la alfombra" para imponer a los invariantes.
Tipos de datos abstractos en OCaml
En OCaml, los módulos se utilizan para estructurar un programa. Un módulo tiene una implementación y una firma , siendo este último un tipo de resumen de valores y tipos definidos en el módulo, mientras que el primero proporciona las definiciones reales. Esto se puede comparar libremente con el díptico
.c/.h
familiar para los programadores de C.Como ejemplo, podemos implementar el
Fraction
módulo de esta manera:Esta definición ahora se puede usar así:
Cualquiera puede producir valores de la fracción tipo directamente, sin pasar por la red de seguridad incorporada
Fraction.make
:Para evitar esto, es posible ocultar la definición concreta del tipo
Fraction.t
así:La única forma de crear un
AbstractFraction.t
es usar laAbstractFraction.make
función.Tipos de datos abstractos en esquema
El lenguaje Scheme no tiene el mismo mecanismo de tipos de datos abstractos que OCaml. Se basa en el usuario "no mirar debajo de la alfombra" para lograr la encapsulación.
En Scheme, es costumbre definir predicados como
fraction?
reconocer valores que dan la oportunidad de validar la entrada. En mi experiencia, el uso dominante es permitir que el usuario valide su entrada, si falsifica un valor, en lugar de validar la entrada en cada llamada a la biblioteca.Sin embargo, existen varias estrategias para imponer la abstracción de los valores devueltos, como devolver un cierre que produce el valor cuando se aplica o devolver una referencia a un valor en un grupo administrado por la biblioteca, pero nunca vi ninguno de ellos en la práctica.
fuente
La encapsulación no es una característica que vino con OOP. Cualquier lenguaje que admita una modularización adecuada lo tiene.
A continuación, te mostramos cómo hacerlo en Haskell:
Ahora, para crear un Rational, utiliza la función de relación, que aplica la invariante. Debido a que los datos son inmutables, no puede violar más tarde la invariante.
Sin embargo, esto le cuesta algo: ya no es posible que el usuario use la misma declaración de deconstrucción que el uso de denominador y numerador.
fuente
Lo haces de la misma manera: crea un constructor que aplique la restricción y acepta usar ese constructor cada vez que crees un nuevo valor.
Pero Karl, en OOP no tienes que aceptar usar el constructor. ¿Oh enserio?
De hecho, las oportunidades para este tipo de abuso son menores en FP. Usted tiene que poner el constructor pasado, debido a la inmutabilidad. Desearía que la gente dejara de pensar en la encapsulación como una especie de protección contra colegas incompetentes, o como obviando la necesidad de comunicar restricciones. No hace eso. Simplemente limita los lugares que tiene que consultar. Los buenos programadores de FP también usan encapsulación. Simplemente viene en forma de comunicar algunas funciones preferidas para hacer ciertos tipos de modificaciones.
fuente