design patterns - Principios, mejores prácticas y patrones de diseño para la programación funcional
design-patterns functional-programming (7)
¿Hay algún principio conocido, mejores prácticas y patrones de diseño que se puedan seguir al escribir código en un lenguaje de programación funcional?
Hay pliegues, despliegues, mapas, etc.
Considero usar las mejores prácticas, ya que es bastante fácil razonar sobre su comportamiento, y a menudo comunican el propósito de una función (por ejemplo, solo eche un vistazo a la famosa Evolución de un Programador Haskell y compare a un estudiante de primer año con senior, y con el profesor).
No sigas los principios, sigue tu nariz. Mantenga las funciones cortas. Busque formas de reducir la cantidad de complejidad en el código, que a menudo, pero no necesariamente, significa el código más conciso. Aprenda a usar las funciones integradas de orden superior.
Refactorice y reduzca el tamaño del código de una función inmediatamente después de haberla escrito. Esto ahorra tiempo porque mañana ya no tendrá el problema y la solución en mente.
Patrón de diseño: permita que el compilador deduzca tipos para sus funciones, y asegúrese de que esos tipos sean exactamente tan generales como usted espera. Si los tipos son más polimórficos o menos polimórficos, averigua por qué.
Por ejemplo, si está escribiendo una función de clasificación en Haskell, espere
Ord a => [a] -> [a]
Si tu tipo es
Num a => [a] -> [a]
o
[a] -> b
entonces algo está terriblemente mal.
Práctica recomendada: una vez que haya confirmado con el compilador que el tipo es el esperado, coloque una firma de tipo explícita para cada función de nivel superior . (O si está utilizando ML o Caml, escriba una interfaz explícita). Establezca las opciones del compilador de modo que los valores con firmas faltantes desencadenen un error.
Patrón de diseño: permita que los tipos guíen su codificación .
Averigua qué tipo estás tratando de devolver.
Sepa que ciertos constructores de tipos vienen con cierta sintaxis, y explótelo para hacer que el tipo deseado sea más pequeño. Aquí hay dos ejemplos:
Si está intentando devolver una función tipo
T1 -> T2
, siempre es seguro escribir/ x -> ...
Ahora en el cuerpo está tratando de producir un valor de tipo
T2
, que es un tipo más pequeño, además de que ha ganado un valor extrax
de tipoT1
, lo que puede facilitar su trabajo.Si la lambda resulta ser innecesaria, siempre puede eta-reducirla más tarde.
Si está tratando de producir un par de tipos
(T1, T2)
, siempre puede tratar de producir un valorx
de tipoT1
y un valory
de tipoT2
, y luego formar el par(x, y)
. Nuevamente, redujo el problema a uno con tipos más pequeños.
Una vez que los tipos son tan pequeños como usted puede hacerlos, mire los tipos de todas las variables vinculadas y dependientes de lambda en el alcance, y vea cómo puede producir un valor del tipo que desee. Por lo general, espera usar todos los argumentos para todas las funciones; si no lo haces, asegúrate de explicar por qué.
En muchas situaciones, especialmente cuando se escriben funciones polimórficas, esta técnica de diseño puede reducir la construcción de una función complicada hasta una o dos opciones. Los tipos guían la construcción del programa de modo que hay muy pocas formas de escribir una función del tipo correcto --- y generalmente solo una forma que no es obviamente incorrecta.
Práctica recomendada: use tipos de datos algebraicos y aproveche la verificación exhaustiva del compilador de coincidencia de patrones. En particular,
Nunca coincida con un patrón de comodín
_
en el nivel superior.Establezca las opciones del compilador para que un caso perdido en una coincidencia de patrón sea un error, no una advertencia.
Respondería con un principio tal vez un poco vago: esfuércese por hacer que su código sea hermoso como el aspecto más importante. Para citar a David Gelernter:
La belleza es más importante en informática que en cualquier otro lugar en tecnología porque el software es muy complicado. La belleza es la mejor defensa contra la complejidad.
y Brian Kernighan:
Controlar la complejidad es la esencia de la programación informática.
Si persigue este objetivo, lo guiará a escribir código que sea fácil de leer y comprender (lo cual es muy importante), código que se divide en partes pequeñas y compactas con un propósito bien definido. Te llevará a aprender a expresar tus pensamientos de la mejor manera en un idioma en particular.
(Todo esto no se aplica solo a los lenguajes funcionales, pero escribir códigos hermosos es mucho más fácil en ellos).
Por qué la Programación Funcional Importa por John Hughes proporciona una buena motivación sobre por qué la pereza y las funciones de orden superior (primera clase) proporcionan mucho de lo que faltan idiomas menos funcionales y se complementan con patrones de diseño .
En el contexto de Haskell, pensé que el libro Real World Haskell tenía algunos consejos buenos y prácticos sobre modismos y abstracción y clases de tipo y cosas por el estilo. La Typeclassopedia también es siempre útil. Las clases de tipo básicas, muy abstractas, se podrían considerar patrones de diseño, excepto que son aplicadas por el sistema de compilación / tipo y parte del lenguaje (si se aprende cómo usarlas).
En el contexto de Lisp, Paul Graham escribió un libro llamado On Lisp ( disponible en línea ) donde muestra que los lenguajes funcionales son ideales para crear un lenguaje de programación personalizado y luego escribir su programa en eso. Por lo tanto, los propios lenguajes específicos del dominio incrustado son una especie de patrón de diseño.