Como Darius Bacon observado, este es esencialmente el problema de expresión, un problema de larga data sin una solución universalmente aceptada. Sin embargo, la falta de un enfoque del mejor de los dos mundos no nos impide, a veces, querer ir de un lado a otro. Ahora, solicitó un "patrón de diseño para lenguajes funcionales", así que echemos un vistazo. El ejemplo que sigue está escrito en Haskell, pero no necesariamente es idiomático para Haskell (o cualquier otro idioma).
Primero, una revisión rápida del "problema de expresión". Considere el siguiente tipo de datos algebraica:
data Expr a = Lit a | Sum (Expr a) (Expr a)
exprEval (Lit x) = x
exprEval (Sum x y) = exprEval x + exprEval y
exprShow (Lit x) = show x
exprShow (Sum x y) = unwords ["(", exprShow x, " + ", exprShow y, ")"]
Esto representa expresiones matemáticas sencillas, que sólo contienen valores literales y adición. Con las funciones que tenemos aquí, podemos tomar una expresión y evaluarla, o mostrarla como String
. Ahora, digamos que queremos agregar una nueva función - por ejemplo, mapear una función sobre todos los valores literales:
exprMap f (Lit x) = Lit (f x)
exprMap f (Sum x y) = Sum (exprMap f x) (exprMap f y)
¡Fácil! ¡Podemos seguir escribiendo funciones todo el día sin romper a sudar! ¡Los tipos de datos algebraicos son increíbles!
De hecho, son tan geniales, queremos que nuestro tipo de expresión sea más, errh, expresivo. Vamos a extenderlo para apoyar la multiplicación, vamos a ... uhh ... oh querido, eso va a ser incómodo, ¿no? Tenemos que modificar cada función que acabamos de escribir. ¡Desesperación!
De hecho, tal vez la extensión de las expresiones es más interesante que agregar funciones que las usan. Entonces, digamos que estamos dispuestos a hacer la compensación en la otra dirección. ¿Cómo podríamos hacer eso?
Bueno, no tiene sentido hacer las cosas a medio camino. Vamos a up-end todo e invertimos todo el programa. ¿Qué significa eso? Bueno, esto es programación funcional, ¿y qué es más funcional que las funciones de orden superior? Lo que haremos es reemplazar el tipo de datos que representa valores de expresión con uno que represente acciones en la expresión. En lugar de elegir un constructor que necesitaremos un registro de todas las acciones posibles, algo como esto:
data Actions a = Actions {
actEval :: a,
actMap :: (a -> a) -> Actions a }
Entonces, ¿cómo crear una expresión sin un tipo de datos? Bueno, nuestras funciones ahora son datos, así que supongo que nuestros datos deben ser funciones. Haremos "constructores" utilizando las funciones regulares, devolver un registro de las acciones:
mkLit x = Actions x (\f -> mkLit (f x))
mkSum x y = Actions
(actEval x + actEval y)
(\f -> mkSum (actMap x f) (actMap y f))
se puede añadir la multiplicación más fácilmente ahora? Claro que sí!
mkProd x y = Actions
(actEval x * actEval y)
(\f -> mkProd (actMap x f) (actMap y f))
Oh, pero espera - que se olvidó de añadir una acción actShow
anterior, vamos a añadir que, en, sólo tendremos que ... errh, también.
En cualquier caso, ¿qué aspecto tiene utilizar los dos estilos diferentes?
expr1plus1 = Sum (Lit 1) (Lit 1)
action1plus1 = mkSum (mkLit 1) (mkLit 1)
action1times1 = mkProd (mkLit 1) (mkLit 1)
Más o menos lo mismo, cuando no las está extendiendo.
Como una nota interesante, tenga en cuenta que en el estilo de "acciones", los valores reales en la expresión están completamente ocultos --el campo actEval
sólo promete darnos algo del tipo correcto, la forma en que se ofrece es su propio negocio Gracias a la evaluación perezosa, el contenido del campo puede ser incluso un cálculo elaborado, realizado solo a pedido. Un valor Actions a
es completamente opaco a la inspección externa, presentando solo las acciones definidas al mundo exterior.
Este estilo de programación: reemplaza datos simples con un conjunto de "acciones" mientras oculta los detalles reales de implementación en una caja negra, usando funciones tipo constructor para construir nuevos bits de datos, pudiendo intercambiar "valores" muy diferentes "con el mismo conjunto de" acciones ", etc., es interesante. Probablemente haya un nombre para ello, pero no puedo recordar ...
Algunas discusiones sobre el problema de la expresión aquí: http://lambda-the-ultimate.org/node/1641 parece ayudar, pero todavía no hay respuesta. –
Quizás una solución sería mejores herramientas de refactorización para lenguajes FP. Al menos para Haskell y ML tengo el compilador –