¿Cómo puede existir una función de tiempo en la programación funcional?
Debo admitir que no sé mucho sobre programación funcional. Leí sobre esto de aquí y de allá, y descubrí que en la programación funcional, una función devuelve la misma salida, para la misma entrada, sin importar cuántas veces se llame a la función. Es exactamente como una función matemática que evalúa la misma salida para el mismo valor de los parámetros de entrada que intervienen en la expresión de la función.
Por ejemplo, considere esto:
f(x,y) = x*x + y; // It is a mathematical function
No importa cuantas veces lo uses f(10,4)
, su valor siempre será 104
. Como tal, dondequiera que haya escrito f(10,4)
, puede reemplazarlo con 104
, sin alterar el valor de toda la expresión. Esta propiedad se conoce como transparencia referencial de una expresión.
Como dice Wikipedia ( enlace ),
Por el contrario, en el código funcional, el valor de salida de una función depende solo de los argumentos que se ingresan a la función, por lo que llamar a una función f dos veces con el mismo valor para un argumento x producirá el mismo resultado f(x) ambas veces.
¿ Puede existir una función de tiempo (que devuelve la hora actual ) en la programación funcional?
En caso afirmativo, ¿cómo puede existir? ¿No viola el principio de programación funcional? En particular, viola la transparencia referencial , que es una de las propiedades de la programación funcional (si la entiendo correctamente).
O si no, ¿cómo se puede saber la hora actual en la programación funcional?
Si y no.
Los distintos lenguajes de programación funcional los resuelven de forma diferente.
En Haskell (uno muy puro), todo esto tiene que suceder en algo llamado I/O Monad (ver aquí ).
Puede pensar en ello como obtener otra entrada (y salida) a su función (el estado mundial) o más fácilmente como un lugar donde ocurre la "impura", como el cambio de tiempo.
Otros lenguajes como F# simplemente tienen cierta impureza incorporada, por lo que puedes tener una función que devuelva diferentes valores para la misma entrada, al igual que los lenguajes imperativos normales .
Como mencionó Jeffrey Burka en su comentario: Aquí está una buena introducción a la mónada de E/S directamente desde la wiki de Haskell.
Otra forma de explicarlo es la siguiente: ninguna función puede obtener la hora actual (ya que sigue cambiando), pero una acción puede obtener la hora actual. Digamos que getClockTime
es una constante (o una función nula, si se prefiere) que representa la acción de obtener la hora actual. Esta acción es la misma cada vez, sin importar cuándo se use, por lo que es una constante real.
Asimismo, digamos print
que es una función que toma algún tiempo de representación y la imprime en la consola. Dado que las llamadas a funciones no pueden tener efectos secundarios en un lenguaje funcional puro, imaginamos que es una función que toma una marca de tiempo y devuelve la acción de imprimirla en la consola. Nuevamente, esta es una función real, porque si le asigna la misma marca de tiempo, devolverá la misma acción de imprimirla cada vez.
Ahora bien, ¿cómo se puede imprimir la hora actual en la consola? Bueno, hay que combinar las dos acciones. Entonces, ¿cómo podemos hacer eso? No podemos simplemente pasar getClockTime
a print
, ya que print espera una marca de tiempo, no una acción. Pero podemos imaginar que hay un operador >>=
que combina dos acciones, una que obtiene una marca de tiempo y otra que toma una como argumento y la imprime. Aplicando esto a las acciones mencionadas anteriormente, el resultado es... tadaaa... una nueva acción que obtiene la hora actual y la imprime. Y, dicho sea de paso, así es exactamente como se hace en Haskell.
Prelude> System.Time.getClockTime >>= print
Fri Sep 2 01:13:23 東京 (標準時) 2011
Entonces, conceptualmente, puede verlo de esta manera: un programa puramente funcional no realiza ninguna E/S, define una acción , que luego ejecuta el sistema de ejecución. La acción es la misma cada vez, pero el resultado de ejecutarla depende de las circunstancias del momento en que se ejecuta.
No sé si esto fue más claro que las otras explicaciones, pero a veces me ayuda pensarlo de esta manera.
En Haskell se utiliza una construcción llamada mónada para manejar los efectos secundarios. Una mónada básicamente significa que encapsulas valores en un contenedor y tienes algunas funciones para encadenar funciones de valores a valores dentro de un contenedor. Si nuestro contenedor es del tipo:
data IO a = IO (RealWorld -> (a,RealWorld))
podemos implementar acciones de IO de forma segura. Este tipo significa: Una acción de tipo IO
es una función que toma un token de tipo RealWorld
y devuelve un token nuevo, junto con un resultado.
La idea detrás de esto es que cada acción de IO muta el estado exterior, representado por la ficha mágica RealWorld
. Usando mónadas, uno puede encadenar múltiples funciones que mutan el mundo real juntas. La función más importante de una mónada es >>=
, pronunciada, vincular :
(>>=) :: IO a -> (a -> IO b) -> IO b
>>=
toma una acción y una función que toma el resultado de esta acción y crea una nueva acción a partir de ella. El tipo de devolución es la nueva acción. Por ejemplo, supongamos que hay una función now :: IO String
que devuelve una cadena que representa la hora actual. Podemos encadenarlo con la función putStrLn
para imprimirlo:
now >>= putStrLn
O escrito en do
-Notación, que resulta más familiar para un programador imperativo:
do currTime <- now
putStrLn currTime
Todo esto es puro, ya que asignamos la mutación y la información sobre el mundo exterior al RealWorld
token. Entonces, cada vez que ejecutas esta acción, obtienes, por supuesto, una salida diferente, pero la entrada no es la misma: el RealWorld
token es diferente.
La mayoría de los lenguajes de programación funcionales no son puros, es decir, permiten que las funciones no dependan sólo de sus valores. En esos idiomas es perfectamente posible tener una función que devuelva la hora actual. De los idiomas con los que etiquetó esta pregunta, esto se aplica a Scala y F# (así como a la mayoría de las otras variantes de ML ).
En lenguajes como Haskell y Clean , que son puros, la situación es diferente. En Haskell, la hora actual no estaría disponible a través de una función, sino a través de la llamada acción IO, que es la forma en que Haskell encapsula los efectos secundarios.
En Clean sería una función, pero la función tomaría un valor mundial como argumento y devolvería un valor mundial nuevo (además de la hora actual) como resultado. El sistema de tipos garantizaría que cada valor mundial pueda usarse solo una vez (y cada función que consuma un valor mundial produciría uno nuevo). De esta manera, la función de tiempo tendría que llamarse con un argumento diferente cada vez y, por lo tanto, se le permitiría devolver una hora diferente cada vez.