scala - ejemplos - programacion funcional vs orientada a objetos
¿Cómo puede existir una función de tiempo en la programación funcional? (13)
Si es así, ¿cómo puede existir? ¿No viola el principio de programación funcional? Viola especialmente la transparencia referencial.
No existe en un sentido puramente funcional.
O si no, entonces ¿cómo se puede saber la hora actual en la programación funcional?
Primero puede ser útil saber cómo se recupera una hora en una computadora. Esencialmente, hay circuitos integrados que realizan un seguimiento del tiempo (razón por la cual una computadora usualmente necesita una batería de celda pequeña). Entonces podría haber algún proceso interno que establezca el valor del tiempo en un determinado registro de memoria. Lo que esencialmente se reduce a un valor que puede ser recuperado por la CPU.
Para Haskell, hay un concepto de ''acción de IO'' que representa un tipo que se puede hacer para llevar a cabo algún proceso de IO. Entonces, en lugar de hacer referencia a un valor de time
, hacemos referencia a un valor de IO Time
Todo esto sería puramente funcional. No estamos haciendo referencia al time
sino a algo como "leer el valor del registro de tiempo" .
Cuando realmente ejecutamos el programa Haskell, la acción IO realmente tendría lugar.
Tengo que admitir que no sé mucho sobre programación funcional. Lo leí de aquí y de allí, y así llegué a saber que en la programación funcional, una función devuelve la misma salida, para la misma entrada, sin importar cuántas veces se llame 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 implica la expresión de la función.
Por ejemplo, considera esto:
f(x,y) = x*x + y; // It is a mathematical function
No importa cuántas veces uses f(10,4)
, su valor siempre será 104
. Como tal, siempre que hayas escrito f(10,4)
, puedes reemplazarlo por 104
, sin alterar el valor de la expresión completa. Esta propiedad se conoce como transparencia referencial de una expresión.
Como dice la Wikipedia ( link ),
A la inversa, 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) en ambas ocasiones.
¿Puede existir una función de tiempo (que devuelve el tiempo actual ) en la programación funcional?
Si es así, ¿cómo puede existir? ¿No viola el principio de programación funcional? Viola particularmente la transparencia referencial, que es una de las propiedades de la programación funcional (si la comprendo correctamente).
O si no, entonces ¿cómo se puede saber la hora actual en la programación funcional?
"Hora actual" no es una función. Es un parámetro. Si su código depende de la hora actual, significa que su código está parametrizado por tiempo.
¡Sí! ¡Estás en lo correcto! Now () o CurrentTime () o cualquier firma de método de tal sabor no está mostrando transparencia referencial de una manera. Pero por instrucciones para el compilador está parametrizado por una entrada de reloj del sistema.
Por salida, Ahora () puede parecer que no se sigue la transparencia referencial. Pero el comportamiento real del reloj del sistema y la función que se encuentra encima se adhiere a la transparencia referencial.
Absolutamente se puede hacer de una manera puramente funcional. Hay varias formas de hacerlo, pero la más simple es hacer que la función de tiempo devuelva no solo el tiempo, sino también la función a la que debe llamar para obtener la siguiente medición de tiempo .
En C # podrías implementarlo así:
// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
public static readonly ClockStamp ProgramStartTime = new ClockStamp();
public readonly DateTime Time;
private ClockStamp _next;
private ClockStamp() {
this.Time = DateTime.Now;
}
public ClockStamp NextMeasurement() {
if (this._next == null) this._next = new ClockStamp();
return this._next;
}
}
(Tenga en cuenta que este es un ejemplo destinado a ser simple, no práctico. En particular, los nodos de la lista no pueden recogerse como basura porque están arraigados por ProgramStartTime).
Esta clase ''ClockStamp'' actúa como una lista enlazada inmutable, pero en realidad los nodos se generan a pedido para que puedan contener el tiempo ''actual''. Cualquier función que quiera medir la hora debe tener un parámetro ''clockStamp'' y también debe devolver su última medición de tiempo en su resultado (para que la persona que llama no vea las mediciones anteriores), como esto:
// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
public readonly ClockStamp Time;
public readonly T Value;
public TimeStampedValue(ClockStamp time, T value) {
this.Time = time;
this.Value = value;
}
}
// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
var start = lastMeasurement.NextMeasurement();
for (var i = 0; i < 10000000; i++) {
}
var end = start.NextMeasurement();
var duration = end.Time - start.Time;
return new TimeStampedValue<TimeSpan>(end, duration);
}
public static void Main(String[] args) {
var clock = ClockStamp.ProgramStartTime;
var r = TimeALoop(clock);
var duration = r.Value; //the result
clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}
Por supuesto, es un poco incómodo tener que pasar esa última medición dentro y fuera, dentro y fuera, dentro y fuera. Hay muchas formas de ocultar la placa, especialmente en el nivel de diseño del idioma. Creo que Haskell usa este tipo de truco y luego oculta las partes feas usando mónadas.
En Haskell uno usa una construcción llamada mónada para manejar los efectos secundarios. Una mónada básicamente significa que encapsula valores en un contenedor y tiene algunas funciones para encadenar funciones de valores a valores dentro de un contenedor. Si nuestro contenedor tiene el tipo:
data IO a = IO (RealWorld -> (a,RealWorld))
Podemos implementar de forma segura las acciones de IO. 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 IO muta el estado externo, representado por el token mágico RealWorld
. Mediante el uso de mónadas, se pueden encadenar múltiples funciones que mutan el mundo real. La función más importante de una mónada es >>=
, se pronuncia bind :
(>>=) :: 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 esto. El tipo de retorno 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
-Notation, que es más familiar para un programador imperativo:
do currTime <- now
putStrLn currTime
Todo esto es puro, ya que mapeamos la mutación y la información sobre el mundo exterior en el token de RealWorld
. Entonces, cada vez que ejecuta esta acción, obtiene, por supuesto, una salida diferente, pero la entrada no es la misma: el token de RealWorld
es diferente.
Estás abordando un tema muy importante en la programación funcional, es decir, realizar E / S. La forma en que lo hacen muchos lenguajes puros es mediante el uso de lenguajes integrados de dominios específicos, por ejemplo, un sublenguaje cuya tarea es codificar acciones , que pueden tener resultados.
El tiempo de ejecución de Haskell, por ejemplo, espera que yo defina una acción llamada main
que se compone de todas las acciones que conforman mi programa. El tiempo de ejecución ejecuta esta acción. La mayoría de las veces, al hacerlo, ejecuta código puro. De vez en cuando, el tiempo de ejecución utilizará los datos computados para realizar la E / S y devolverá los datos a código puro.
Puede quejarse de que esto suene a trampa, y en cierto modo lo es: al definir acciones y esperar que el tiempo de ejecución las ejecute, el programador puede hacer todo lo que un programa normal puede hacer. Pero el fuerte sistema de tipos de Haskell crea una fuerte barrera entre las partes puras e "impuras" del programa: no puede simplemente agregar, digamos, dos segundos al tiempo actual de la CPU, e imprimirlo, debe definir una acción que resulte en el actual Tiempo de CPU y pase el resultado a otra acción que agrega dos segundos e imprime el resultado. Sin embargo, escribir demasiado de un programa se considera mal estilo, porque hace que sea difícil inferir qué efectos se producen, en comparación con los tipos de Haskell que nos dicen todo lo que podemos saber sobre qué es un valor.
Ejemplo: clock_t c = time(NULL); printf("%d/n", c + 2);
clock_t c = time(NULL); printf("%d/n", c + 2);
en C, vs. main = getCPUTime >>= /c -> print (c + 2*1000*1000*1000*1000)
en Haskell. El operador >>=
se utiliza para componer acciones, pasando el resultado de la primera a una función que resulta en la segunda acción. Con un aspecto bastante arcano, los compiladores de Haskell soportan el azúcar sintáctico que nos permite escribir el último código de la siguiente manera:
type Clock = Integer -- To make it more similar to the C code
-- An action that returns nothing, but might do something
main :: IO ()
main = do
-- An action that returns an Integer, which we view as CPU Clock values
c <- getCPUTime :: IO Clock
-- An action that prints data, but returns nothing
print (c + 2*1000*1000*1000*1000) :: IO ()
Este último parece bastante imperativo, ¿no es así?
La mayoría de los lenguajes de programación funcionales no son puros, es decir, permiten que las funciones no solo dependan de sus valores. En esos idiomas es perfectamente posible tener una función que devuelva la hora actual. De los idiomas con los que marcó 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 de una acción llamada IO, que es la forma en que Haskell encapsula los efectos secundarios.
En Limpiar 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 del tiempo actual) como su resultado. El sistema de tipos se aseguraría de que cada valor mundial se pueda usar una sola vez (y cada función que consuma un valor mundial produciría una nueva). 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 un tiempo diferente cada vez.
Me sorprende que ninguna de las respuestas o comentarios mencionen coalgebras o coinduction. Por lo general, la coinducción se menciona al razonar sobre estructuras de datos infinitas, pero también es aplicable a un sinfín de observaciones, como un registro de tiempo en una CPU. Un coalgebra modela el estado oculto; y modelos de coinducción observando ese estado. (Modelos de inducción normal construyendo estado.)
Este es un tema candente en la programación funcional reactiva. Si estás interesado en este tipo de cosas, lee esto: http://digitalcommons.ohsu.edu/csetech/91/ (28 pp.)
Otra forma de explicarlo es esta: 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 lo desea) que representa la acción de obtener la hora actual. Esta acción es la misma cada vez que se usa, por lo que es una constante real.
Del mismo modo, digamos que print
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, en cambio imaginamos que es una función que toma una marca de tiempo y devuelve la acción de imprimirla a la consola. Nuevamente, esta es una función real, porque si le asignas la misma marca de tiempo, devolverá la misma acción de imprimirla cada vez.
Ahora, ¿cómo puedes imprimir la hora actual en la consola? Bueno, tienes que combinar las dos acciones. Entonces, ¿cómo podemos hacer eso? No podemos simplemente pasar getClockTime
para print
, ya que la impresión 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 hora 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 esto es, por cierto, 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 funcional puro no realiza ninguna E / S, define una acción , que luego ejecuta el sistema de tiempo de ejecución. La acción es la misma cada vez, pero el resultado de su ejecución depende de las circunstancias en que se ejecuta.
No sé si esto fue más claro que las otras explicaciones, pero a veces me ayuda a pensar de esta manera.
Sí, es posible que una función pura devuelva el tiempo, si se le da ese tiempo como parámetro. Argumento de tiempo diferente, resultado de tiempo diferente. Luego forme otras funciones del tiempo también y combínelas con un vocabulario simple de funciones de función (-t-time) -transforming (orden superior). Dado que el enfoque no tiene estado, el tiempo aquí puede ser continuo (independiente de la resolución) en lugar de discreto, lo que aumenta enormemente la modularidad . Esta intuición es la base de la Programación reactiva funcional (FRP).
Sí, puede existir una función de obtención de tiempo en la programación funcional utilizando una versión ligeramente modificada en la programación funcional conocida como programación funcional impura (la predeterminada o la principal es la programación funcional pura).
En caso de obtener el tiempo (o leer un archivo, o lanzar un misil), el código debe interactuar con el mundo exterior para realizar el trabajo y este mundo exterior no se basa en los fundamentos puros de la programación funcional. Para permitir que un mundo de programación funcional puro interactúe con este mundo exterior impuro, las personas han introducido programación funcional impura. Después de todo, el software que no interactúa con el mundo exterior no es más útil que hacer algunos cálculos matemáticos.
Pocos lenguajes de programación de programación funcional tienen incorporada esta característica de impureza, por lo que no es fácil separar qué código es impuro y cuál es puro (como F #, etc.) y algunos lenguajes de programación funcionales se aseguran de que al hacer algunas cosas impuras ese código es claramente destacado en comparación con el código puro, como Haskell.
Otra forma interesante de ver esto sería que su función de obtención de tiempo en la programación funcional tomaría un objeto de "mundo" que tiene el estado actual del mundo, como el tiempo, el número de personas que viven en el mundo, etc. El objeto siempre será puro, es decir, si pasa en el mismo estado mundial, siempre obtendrá el mismo tiempo.
Si y no.
Diferentes lenguajes de programación funcionales los resuelven de manera diferente.
En Haskell (uno muy puro) todo esto tiene que suceder en algo que se llama la Mónada de E / S , consulte here .
Puede pensar que es obtener otra entrada (y salida) en su función (el estado del mundo) o más fácil como un lugar donde ocurre la "impureza", como el cambio de tiempo.
Otros lenguajes como F # solo tienen algo de impureza incorporado, por lo que puede tener una función que devuelve diferentes valores para la misma entrada, al igual que los lenguajes imperativos normales .
Como Jeffrey Burka mencionó en su comentario: Aquí está la buena introducción a la E / S Monad directamente desde el wiki de Haskell.
Su pregunta combina dos medidas relacionadas de un lenguaje informático: funcional / imperativo y puro / impuro.
Un lenguaje funcional define las relaciones entre entradas y salidas de funciones, y un lenguaje imperativo describe operaciones específicas en un orden específico para realizar.
Un lenguaje puro no crea ni depende de los efectos secundarios, y un lenguaje impuro los usa en todas partes.
Los programas puros al cien por cien son básicamente inútiles. Pueden realizar un cálculo interesante, pero como no pueden tener efectos secundarios, no tienen entrada ni salida, por lo que nunca sabría lo que calcularon.
Para ser útil en absoluto, un programa debe ser al menos un impuro. Una forma de hacer que un programa puro sea útil es colocarlo dentro de una envoltura delgada e impura. Como este programa Haskell no probado:
-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
putStrLn "Please enter the input parameter"
inputStr <- readLine
putStrLn "Starting time:"
getCurrentTime >>= print
let inputInt = read inputStr -- this line is pure
let result = fib inputInt -- this is also pure
putStrLn "Result:"
print result
putStrLn "Ending time:"
getCurrentTime >>= print