functional programming - programacion - ¿Cuál es la diferencia entre la programación de procedimientos y la programación funcional?
programacion imperativa (16)
@Creighton:
En Haskell hay una función de biblioteca llamada producto :
prouduct list = foldr 1 (*) list
o simplemente:
product = foldr 1 (*)
así el factorial "idiomático"
fac n = foldr 1 (*) [1..n]
simplemente sería
fac n = product [1..n]
He leído los artículos de Wikipedia para la programación de procedimientos y la programación funcional , pero todavía estoy un poco confundido. ¿Alguien podría reducirlo hasta la médula?
Básicamente los dos estilos, son como Yin y Yang. Uno está organizado, mientras que el otro caótico. Hay situaciones en las que la programación funcional es la opción obvia, y otras situaciones donde la programación de procedimientos es la mejor opción. Es por esto que hay al menos dos lenguajes que recientemente han salido con una nueva versión, que abarca ambos estilos de programación. ( Perl 6 y D 2 )
Procesal:
- La salida de una rutina no siempre tiene una correlación directa con la entrada.
- Todo se hace en un orden específico.
- La ejecución de una rutina puede tener efectos secundarios.
- Tiende a enfatizar la implementación de soluciones de forma lineal.
Perl 6
sub factorial ( UInt:D $n is copy ) returns UInt {
# modify "outside" state
state $call-count++;
# in this case it is rather pointless as
# it can''t even be accessed from outside
my $result = 1;
loop ( ; $n > 0 ; $n-- ){
$result *= $n;
}
return $result;
}
D 2
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
Funcional:
- A menudo recursivo.
- Siempre devuelve la misma salida para una entrada dada.
- El orden de evaluación suele ser indefinido.
- Debe ser apátrida. Es decir, ninguna operación puede tener efectos secundarios.
- Buen ajuste para ejecución paralela
- Tiende a enfatizar un enfoque de dividir y conquistar.
- Puede tener la característica de Evaluación perezosa.
Haskell
(Copiado de Haskell );
fac :: Integer -> Integer
fac 0 = 1
fac n | n > 0 = n * fac (n-1)
o en una línea:
fac n = if n > 0 then n * fac (n-1) else 1
Perl 6
proto sub factorial ( UInt:D $n ) returns UInt {*}
multi sub factorial ( 0 ) { 1 }
multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 }
D 2
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
Nota al margen:
Factorial es en realidad un ejemplo común para mostrar lo fácil que es crear nuevos operadores en Perl 6 de la misma manera que crearía una subrutina. Esta característica está tan arraigada en Perl 6 que la mayoría de los operadores en la implementación de Rakudo se definen de esta manera. También le permite agregar sus propios candidatos múltiples a los operadores existentes.
sub postfix:< ! > ( UInt:D $n --> UInt )
is tighter(&infix:<*>)
{ [*] 2 .. $n }
say 5!; # 120
Este ejemplo también muestra la creación de rango ( 2..$n
) y el meta-operador de reducción de lista ( [ OPERATOR ] LIST
) combinados con el operador de multiplicación de infijo numérico. ( *
)
También muestra que puede poner --> UInt
en la firma en lugar de returns UInt
después de ella.
(Puede salirse de comenzar el rango con 2
ya que el "operador" multiplicado devolverá 1
cuando se le llame sin ningún argumento)
Creo que la programación procesal / funcional / objetiva trata sobre cómo abordar un problema.
El primer estilo planearía todo en pasos y soluciona el problema implementando un paso (un procedimiento) a la vez. Por otro lado, la programación funcional enfatizaría el enfoque de dividir y vencer, donde el problema se divide en subproblema, luego se resuelve cada subproblema (creando una función para resolver ese subproblema) y los resultados se combinan para Crea la respuesta para todo el problema. Por último, la programación objetiva imitaría el mundo real al crear un mini mundo dentro de la computadora con muchos objetos, cada uno de los cuales tiene características únicas (algo) e interactúa con otros. De esas interacciones surgiría el resultado.
Cada estilo de programación tiene sus propias ventajas y debilidades. Por lo tanto, hacer algo como "programación pura" (es decir, puramente de procedimiento, nadie hace esto, por cierto, que es algo extraño, o puramente funcional o puramente objetivo) es muy difícil, si no imposible, excepto algunos problemas elementales, especialmente diseñado para demostrar la ventaja de un estilo de programación (por lo tanto, llamamos a aquellos a quienes les gusta la pureza "weenie": D).
Luego, a partir de esos estilos, tenemos lenguajes de programación que están diseñados para optimizar cada estilo. Por ejemplo, la Asamblea tiene que ver con los procedimientos. De acuerdo, la mayoría de los idiomas tempranos son de procedimiento, no solo Asm, como C, Pascal (y Fortran, escuché). Luego, todos tenemos Java famoso en la escuela objetiva (en realidad, Java y C # también están en una clase llamada "orientada al dinero", pero eso está sujeto a otra discusión). También objetivo es Smalltalk. En la escuela funcional, tendríamos "casi funcionales" (algunos consideraron que eran impuros) la familia Lisp y la familia ML y muchos Haskell, Erlang, "puramente funcionales". Por cierto, hay muchos idiomas generales como Perl, Python Ruby
En informática, la programación funcional es un paradigma de programación que trata la computación como la evaluación de funciones matemáticas y evita el estado y los datos mutables. Enfatiza la aplicación de funciones, en contraste con el estilo de programación de procedimientos que enfatiza los cambios de estado.
Konrad dijo:
Como consecuencia, un programa puramente funcional siempre produce el mismo valor para una entrada, y el orden de evaluación no está bien definido; lo que significa que los valores inciertos como la entrada del usuario o los valores aleatorios son difíciles de modelar en lenguajes puramente funcionales.
El orden de evaluación en un programa puramente funcional puede ser difícil de entender (especialmente con la pereza) o incluso sin importancia, pero creo que decir que no está bien definido hace que parezca que no se puede saber si su programa va a funcionar. para trabajar en absoluto!
Quizás una mejor explicación sería que el flujo de control en los programas funcionales se basa en cuándo se necesita el valor de los argumentos de una función. Lo bueno de esto es que, en programas bien escritos, el estado se vuelve explícito: cada función enumera sus entradas como parámetros en lugar de arbitrar arbitrariamente el estado global. Entonces, en algún nivel, es más fácil razonar sobre el orden de evaluación con respecto a una función a la vez . Cada función puede ignorar el resto del universo y enfocarse en lo que necesita hacer. Cuando se combinan, se garantiza que las funciones funcionarán igual [1] que en forma aislada.
... los valores inciertos como la entrada del usuario o los valores aleatorios son difíciles de modelar en lenguajes puramente funcionales.
La solución al problema de entrada en programas puramente funcionales es integrar un lenguaje imperativo como un DSL usando una abstracción suficientemente poderosa . En lenguajes imperativos (o funcionales no puros) esto no es necesario porque puede "hacer trampa" y pasar el estado implícitamente y el orden de evaluación es explícito (le guste o no). Debido a este "engaño" y la evaluación forzada de todos los parámetros para cada función, en lenguajes imperativos 1) pierde la capacidad de crear sus propios mecanismos de flujo de control (sin macros), 2) el código no es intrínsecamente seguro y / o paralelizable por defecto , 3) e implementar algo como deshacer (viaje en el tiempo) requiere un trabajo cuidadoso (el programador imperativo debe almacenar una receta para recuperar los valores anteriores), mientras que la programación funcional pura le ofrece todas estas cosas, y algunas más. Puede que haya olvidado "gratis".
Espero que esto no suene a fanatismo, solo quería agregar algo de perspectiva. La programación imperativa y, especialmente, la programación de paradigmas mixtos en lenguajes poderosos como C # 3.0 son todavía formas totalmente efectivas de hacer las cosas y no hay una solución mágica .
[1] ... excepto posiblemente con respecto al uso de la memoria (cf. foldl y foldl ''en Haskell).
La programación de procedimientos divide las secuencias de sentencias y construcciones condicionales en bloques separados llamados procedimientos que se parametrizan sobre argumentos que son valores (no funcionales).
La programación funcional es la misma, excepto que las funciones son valores de primera clase, por lo que pueden pasarse como argumentos a otras funciones y devolverse como resultados de llamadas a funciones.
Tenga en cuenta que la programación funcional es una generalización de la programación de procedimientos en esta interpretación. Sin embargo, una minoría interpreta que "programación funcional" significa que no tiene efectos secundarios, lo cual es bastante diferente pero irrelevante para todos los lenguajes funcionales principales, excepto Haskell.
Los lenguajes de procedimiento tienden a mantener un registro del estado (usando variables) y tienden a ejecutarse como una secuencia de pasos. Los lenguajes puramente funcionales no hacen un seguimiento del estado, utilizan valores inmutables y tienden a ejecutarse como una serie de dependencias. En muchos casos, el estado de la pila de llamadas contendrá la información que sería equivalente a la que se almacenaría en las variables de estado en el código de procedimiento.
La recursión es un ejemplo clásico de programación de estilo funcional.
Ninguna de las respuestas aquí muestra programación funcional idiomática. La respuesta factorial recursiva es excelente para representar la recursión en FP, pero la mayoría del código no es recursivo, por lo que no creo que esa respuesta sea totalmente representativa.
Digamos que tienes una matriz de cadenas, y cada cadena representa un número entero como "5" o "-200". Desea verificar esta matriz de entrada de cadenas en su caso de prueba interno (utilizando la comparación de enteros). Ambas soluciones se muestran a continuación
Procesal
arr_equal(a : [Int], b : [Str]) -> Bool {
if(a.len != b.len) {
return false;
}
bool ret = true;
for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
int a_int = a[i];
int b_int = parseInt(b[i]);
ret &= a_int == b_int;
}
return ret;
}
Funcional
eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization
arr_equal(a : [Int], b : [Str]) -> Bool =
zip(a, b.map(toInt)) # Combines into [Int, Int]
.map(eq)
.reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value
Mientras que los lenguajes puramente funcionales son generalmente lenguajes de investigación (como al mundo real le gustan los efectos secundarios gratuitos), los lenguajes de procedimiento del mundo real usarán la sintaxis funcional mucho más simple cuando sea apropiado.
Esto generalmente se implementa con una biblioteca externa como Lodash , o está disponible con idiomas más nuevos como Rust . El trabajo pesado de la programación funcional se realiza con funciones / conceptos como map
, filter
, reduce
, currying
, partial
, los últimos tres de los cuales puede consultar para una mayor comprensión.
Apéndice
Para ser utilizado en la naturaleza, el compilador normalmente tendrá que encontrar la forma de convertir la versión funcional en la versión de procedimiento internamente, ya que la sobrecarga de llamadas a la función es demasiado alta. Los casos recursivos, como el factorial que se muestra, usarán trucos como la llamada de cola para eliminar el uso de memoria O (n). El hecho de que no haya efectos secundarios permite que los compiladores funcionales implementen la optimización de && ret
incluso cuando el .reduce
se realiza por última vez. El uso de Lodash en JS, obviamente, no permite ninguna optimización, por lo que es un éxito para el rendimiento (que no suele ser una preocupación con el desarrollo web). Los lenguajes como Rust se optimizarán internamente (y tendrán funciones como try_fold
para ayudar a & & && ret
optimización).
Nunca he visto esta definición dada en otra parte, pero creo que esto resume las diferencias aquí bastante bien:
La programación funcional se centra en las expresiones.
La programación procesal se centra en las declaraciones.
Las expresiones tienen valores. Un programa funcional es una expresión cuyo valor es una secuencia de instrucciones para que la computadora lleve a cabo.
Las declaraciones no tienen valores y, en cambio, modifican el estado de alguna máquina conceptual.
En un lenguaje puramente funcional no habría declaraciones, en el sentido de que no hay manera de manipular el estado (todavía podrían tener una construcción sintáctica llamada "declaración", pero a menos que manipule el estado, no lo llamaría una declaración en este sentido ). En un lenguaje puramente procedimental no habría expresiones, todo sería una instrucción que manipula el estado de la máquina.
Haskell sería un ejemplo de un lenguaje puramente funcional porque no hay manera de manipular el estado. El código de máquina sería un ejemplo de un lenguaje puramente procedimental porque todo en un programa es una declaración que manipula el estado de los registros y la memoria de la máquina.
La parte confusa es que la gran mayoría de los lenguajes de programación contienen expresiones y declaraciones, lo que le permite mezclar paradigmas. Los idiomas se pueden clasificar como más funcionales o más de procedimiento en función de cuánto fomentan el uso de declaraciones frente a expresiones.
Por ejemplo, C sería más funcional que COBOL porque una llamada a la función es una expresión, mientras que llamar a un subprograma en COBOL es una declaración (que manipula el estado de las variables compartidas y no devuelve un valor). Python sería más funcional que C porque le permite expresar la lógica condicional como una expresión que utiliza la evaluación de cortocircuito (test && path1 || path2 en lugar de las declaraciones if). El esquema sería más funcional que Python porque todo en el esquema es una expresión.
Aún puede escribir en un estilo funcional en un lenguaje que fomente el paradigma de procedimiento y viceversa. Es más difícil y / o más incómodo escribir en un paradigma que no es alentado por el lenguaje.
Para ampliar el comentario de Konrad:
Como consecuencia, un programa puramente funcional siempre produce el mismo valor para una entrada, y el orden de evaluación no está bien definido;
Debido a esto, el código funcional es generalmente más fácil de paralelizar. Como no hay (generalmente) efectos secundarios de las funciones, y (generalmente) solo actúan sobre sus argumentos, desaparecen muchos problemas de concurrencia.
La programación funcional también se utiliza cuando necesita poder probar que su código es correcto. Esto es mucho más difícil de hacer con la programación de procedimientos (no es fácil con funcional, pero es aún más fácil).
Descargo de responsabilidad: no he usado la programación funcional en años, y solo recientemente comencé a verla de nuevo, por lo que podría no estar completamente correcto aquí. :)
Para ampliar el comentario de Konrad:
y el orden de evaluación no está bien definido
Algunos lenguajes funcionales tienen lo que se llama la evaluación perezosa. Lo que significa que una función no se ejecuta hasta que se necesita el valor. Hasta ese momento, la función en sí misma es lo que se transmite.
Los lenguajes de procedimiento son el paso 1, el paso 2, el paso 3 ... Si en el paso 2 dice agregar 2 + 2, lo hace correctamente. En la evaluación perezosa, diría que agregue 2 + 2, pero si el resultado nunca se usa, nunca hace la adición.
Para comprender la diferencia, hay que entender que el paradigma "el padrino" de la programación tanto procesal como funcional es la imperativa .
Básicamente, la programación de procedimientos es simplemente una forma de estructurar programas imperativos en los que el método primario de abstracción es el "procedimiento". (O "función" en algunos lenguajes de programación). Incluso la Programación Orientada a Objetos es solo otra forma de estructurar un programa imperativo, donde el estado se encapsula en objetos, se convierte en un objeto con un "estado actual", además este objeto tiene un conjunto de funciones, métodos y otras cosas que le permiten El programador manipula o actualiza el estado.
Ahora, en lo que respecta a la programación funcional, lo esencial en su enfoque es que identifica qué valores tomar y cómo deben transferirse estos valores. (así que no hay estado, ni datos mutables, ya que toma las funciones como valores de primera clase y las pasa como parámetros a otras funciones).
PD: la comprensión de cada paradigma de programación se utiliza para aclarar las diferencias entre todos ellos.
PSS: al final del día, los paradigmas de programación son solo enfoques diferentes para resolver problemas.
PSS: this respuesta quora tiene una gran explicación.
Si tiene la oportunidad, recomendaría obtener una copia de Lisp / Scheme y hacer algunos proyectos en ella. La mayoría de las ideas que últimamente se han convertido en vagones se expresaron en Lisp hace décadas: programación funcional, continuaciones (como cierres), recolección de basura, incluso XML.
Así que sería una buena manera de obtener una ventaja sobre todas estas ideas actuales, y algunas más, como el cálculo simbólico.
Debe saber para qué sirve la programación funcional y para qué no. No es bueno para todo. Algunos problemas se expresan mejor en términos de efectos secundarios, donde la misma pregunta da respuestas diferentes dependiendo de cuándo se hace.
Un lenguaje funcional (idealmente) le permite escribir una función matemática, es decir, una función que toma n argumentos y devuelve un valor. Si se ejecuta el programa, esta función se evalúa lógicamente según sea necesario. 1
Un lenguaje de procedimiento, por otro lado, realiza una serie de pasos secuenciales . (Hay una manera de transformar la lógica secuencial en lógica funcional llamada estilo de paso de continuación ).
Como consecuencia, un programa puramente funcional siempre produce el mismo valor para una entrada, y el orden de evaluación no está bien definido; lo que significa que los valores inciertos como la entrada del usuario o los valores aleatorios son difíciles de modelar en lenguajes puramente funcionales.
1 Como todo lo demás en esta respuesta, eso es una generalización. La propiedad de evaluar una computación cuando se necesita su resultado en lugar de secuencialmente donde se le llama se conoce como "pereza", y no todos los lenguajes funcionales son universalmente perezosos, ni la pereza se restringe a la programación funcional. Más bien, la descripción que se proporciona aquí proporciona un "marco mental" para pensar en diferentes estilos de programación que no son categorías distintas y opuestas, sino ideas fluidas.
Una cosa que no había visto realmente enfatizada aquí es que los lenguajes funcionales modernos como Haskell realmente tienen más funciones de primera clase para el control del flujo que la recursión explícita. No es necesario definir factorial recursivamente en Haskell, como se hizo anteriormente. Creo algo como
fac n = foldr (*) 1 [1..n]
es una construcción perfectamente idiomática, y mucho más cercana en espíritu al uso de un bucle que al uso de la recursión explícita.
Una programación funcional es idéntica a la programación de procedimientos en la que no se utilizan variables globales.