logging - Registro desde un paradigma de programación funcional
functional-programming purely-functional (2)
Echemos un vistazo a la solución monádica de Haskell. La idea detrás del registro es que nuestros cálculos tienen un método adicional que escribe un mensaje en algún lugar "fuera". Hay muchas maneras de representar dichos cálculos, pero una de las más generales es hacer una mónada:
class (Monad m) => MonadWriter w m | m -> w where
tell :: w -> m ()
El tipo w
representa los mensajes y la función tell
es lo que "envía" un mensaje a un cómputo monádico (efecto completo).
Notas:
- El
MonadWriter
de Haskell en realidad es más rico, contiene funciones que permiten examinar y modificarw
, pero dejemos eso de lado por ahora. - El
| m -> w
| m -> w
parte no es realmente importante para la explicación, solo significa quew
está arreglado para unm
dado.
La implementación más utilizada es Writer
, que básicamente es solo un par. Uno de los elementos es el resultado de un cálculo y el otro elemento es una secuencia de mensajes escritos. (En realidad, no es realmente una secuencia, es más general: un monoide, que define las operaciones para combinar varios mensajes en uno solo). Puede examinar la solución de Haskell mirando el módulo Writer . Sin embargo, está escrito de manera más general, utilizando el transformador de mónada WriterT
, así que si no eres un fanático de la mónada, puede ser bastante difícil de leer. Lo mismo se puede hacer en otros lenguajes funcionales, por ejemplo, vea este ejemplo en Scala .
Pero hay otras implementaciones posibles, más orientadas a efectos secundarios (aún funcionales) de la clase de tipo anterior. Podemos definir los mensajes para emitir a un receptor externo, como a stdout, a un archivo, etc. Por ejemplo:
{-# LANGUAGE FunctionalDependencies, TypeSynonymInstances, FlexibleInstances #-}
instance MonadWriter String IO where
tell = putStrLn
Aquí decimos que IO
se puede utilizar como un servicio de registro que escribe String
s en stdout. (Esto es solo un ejemplo simplificado, una implementación completa probablemente tendría un transformador de mónada que agregaría la funcionalidad de tell
a cualquier mónada basada en IO
).
Prefiero mantenerme lo más cerca posible del paradigma funcional, apretando lo más cerca que puedo de lo puramente funcional cuando mi cerebro está preparado para el desafío. Yo uso F # cuando sea posible. Por lo general, me quedo con VB.NET o C # (o VBA, cuando realmente tengo mala suerte). Así que mis idiomas me permiten alejarme bastante del enfoque funcional.
Históricamente, he ignorado el registro y la comunicación con el usuario hasta que obtengo un resultado, simplemente deje que el usuario espere. Ahora estoy tratando de implementar el registro y / o actualizaciones de las barras de estado. Es fácil, porque mis idiomas me permiten escribir en la salida estándar cuando lo deseo. Pero desde un punto de vista puramente funcional, ¿cómo se hace para filtrar información sobre lo que sucede dentro de la función de uno hacia el mundo exterior? ¿Es el registro o la comunicación con el usuario durante el cómputo simplemente contrario al enfoque puramente funcional?
Estoy seguro de que en Haskell uno usaría una mónada. ¿Qué pasa cuando se usan otros idiomas?
Gracias.
Soy nuevo en la programación funcional, pero aquí hay un intento en Scala:
object FunctionalLogging {
type Result = Int
class ResultWithLogging(val log: List[String], val result: Result) {}
def functionWithLogging(log: List[String], arg: String): ResultWithLogging = {
def function(arg: String): Result = arg.length
new ResultWithLogging(log :+ ("Calling function(" + arg +")"), function(arg))
}
val result = functionWithLogging(List(), "Hello world!")
// -- Pure functional code ends here --
println("Result = " + result.result)
println("Log = " + result.log)
}
Es funcional porque no tiene efectos secundarios, pero obviamente el registro es parte de los argumentos de la función y el retorno, por lo que no es muy elegante ni práctico.
Me parece que el registro es un efecto secundario deseable por definición, por lo que si sigue mi definición, la pregunta es cómo aislar lo no funcional del código funcional. En la práctica, probablemente comenzaría con un objeto Scala (posiblemente demasiado como un singleton: un rasgo es probablemente mejor Scala), o un actor para acumular los mensajes de registro y hacer lo que sea necesario con ellos.
Esta es una vista más pragmática: Iniciar sesión en Scala
Editar
Esta pregunta habla sobre Haskell Monads y IO: ¿Qué otras formas se pueden manejar en un lenguaje funcional puro, además de con Monads?