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 al núcleo?
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 al núcleo?
Respuestas:
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 forma 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 valores inciertos como la entrada del usuario o 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. Esta propiedad, que evalúa un cálculo cuando se necesita su resultado en lugar de secuencialmente donde se llama, se conoce como "pereza". No todos los lenguajes funcionales son universalmente perezosos, ni la pereza se limita a la programación funcional. Más bien, la descripción dada aquí proporciona un "marco mental" para pensar sobre diferentes estilos de programación que no son categorías distintas y opuestas, sino ideas fluidas.
Básicamente, los dos estilos son como el Yin y el Yang. Uno está organizado, mientras que el otro es caótico. Hay situaciones en las que la programación funcional es la opción obvia, y otras situaciones en las que la programación de procedimientos es la mejor opción. Es por eso 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 )
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;
}
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
(copiado de Wikipedia );
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
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 }
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
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 metaoperador de reducción de lista ( [ OPERATOR ] LIST
) combinado con el operador de multiplicación de infijo numérico. ( *
)
También muestra que puede poner --> UInt
la firma en lugar de returns UInt
después de ella.
(Puede salirse con el inicio del rango 2
ya que el "operador" de multiplicación regresará 1
cuando se le llame sin ningún argumento)
sub postfix:<!> ($n) { [*] 1..$n }
No operation can have side effects
elaborarlo?
sub foo( $a, $b ){ ($a,$b).pick }
← no siempre devuelve la misma salida para la misma entrada, mientras que lo siguiente sísub foo( $a, $b ){ $a + $b }
Nunca he visto esta definición dada en otra parte, pero creo que resume las diferencias aquí bastante bien:
La programación funcional se centra en las expresiones.
La programación procesal se enfoca en declaraciones
Las expresiones tienen valores. Un programa funcional es una expresión cuyo valor es una secuencia de instrucciones para que la computadora las 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 forma de manipular el estado (aún 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 de procedimiento 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 forma de manipular el estado. El código de máquina sería un ejemplo de un lenguaje puramente de procedimiento 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 confuso es que la gran mayoría de los lenguajes de programación contiene dos expresiones y declaraciones, que le permite mezclar paradigmas. Los lenguajes se pueden clasificar como más funcionales o más procesales en función de cuánto fomentan el uso de enunciados frente a expresiones.
Por ejemplo, C sería más funcional que COBOL porque una llamada de 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 usando la evaluación de cortocircuito (prueba && path1 || path2 en lugar de sentencias if). Scheme sería más funcional que Python porque todo en el esquema es una expresión.
Todavía puede escribir en un estilo funcional en un lenguaje que fomente el paradigma de procedimiento y viceversa. Es simplemente más difícil y / o más incómodo escribir en un paradigma que el lenguaje no fomenta.
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 procesal que enfatiza los cambios de estado.
GetUserContext()
en la función, se pasaría el contexto del usuario. ¿Es esta programación funcional? Gracias por adelantado.
Creo que la programación procesal / funcional / objetiva trata sobre cómo abordar un problema.
El primer estilo planificaría todo en pasos y resuelve el problema implementando un paso (un procedimiento) a la vez. Por otro lado, la programación funcional enfatizaría el enfoque de divide y vencerás, 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 (algo) únicas 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 lo hace, por cierto, que es un poco 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 los que les gusta la pureza "weenie": D).
Luego, a partir de esos estilos, tenemos lenguajes de programación diseñados para optimizarse para algunos de cada estilo. Por ejemplo, la Asamblea se trata de procedimientos. De acuerdo, la mayoría de los primeros lenguajes son de procedimiento, no solo Asm, como C, Pascal, (y Fortran, escuché). Entonces, tenemos todos los famosos Java en la escuela objetiva (en realidad, Java y C # también están en una clase llamada "orientada al dinero", pero eso es tema de otra discusión). También objetivo es Smalltalk. En la escuela funcional, tendríamos la familia Lisp "casi funcional" (algunos los consideraron impuros) y la familia ML y muchos Haskell, Erlang, etc. "puramente funcionales". Por cierto, hay muchos idiomas generales como Perl, Python Ruby
num = 1
def function_to_add_one(num):
num += 1
return num
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
#Final Output: 2
num = 1
def procedure_to_add_one():
global num
num += 1
return num
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
#Final Output: 6
function_to_add_one
es una función
procedure_to_add_one
es un procedimiento
Incluso si ejecuta la función cinco veces, cada vez devolverá 2
Si ejecuta el procedimiento cinco veces, al final de la quinta ejecución le dará 6 .
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. Dado que (en general) no hay efectos secundarios de las funciones, y (en general) simplemente actúan sobre sus argumentos, desaparecen muchos problemas de concurrencia.
La programación funcional también se utiliza cuando necesita ser capaz de demostrar 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 aún más fácil).
Descargo de responsabilidad: no he usado programación funcional en años, y solo recientemente comencé a mirarla nuevamente, por lo que es posible que no esté completamente correcto aquí. :)
Una cosa que no había visto realmente enfatizada aquí es que los lenguajes funcionales modernos como Haskell realmente más en funciones de primera clase para el control de flujo que la recursividad explícita. No necesita definir factorial recursivamente en Haskell, como se hizo anteriormente. Pienso 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 recursividad explícita.
Los lenguajes de procedimiento tienden a realizar un seguimiento del estado (usando variables) y tienden a ejecutarse como una secuencia de pasos. Los lenguajes puramente funcionales no hacen un seguimiento del estado, usan 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.
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 razonar (especialmente con 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 mezclar arbitrariamente el estado global. Entonces, en cierto 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 centrarse en lo que necesita hacer. Cuando se combinan, se garantiza que las funciones funcionarán igual [1] que lo harían de 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 incrustar un lenguaje imperativo como DSL utilizando una abstracción suficientemente potente . En lenguajes imperativos (o funcionales no puros) esto no es necesario porque puede "engañar" y pasar el estado implícitamente y el orden de evaluación es explícito (le guste o no). Debido a esta "trampa" y evaluación forzada de todos los parámetros para cada función, en los lenguajes imperativos 1) pierde la capacidad de crear sus propios mecanismos de flujo de control (sin macros), 2) el código no es inherentemente seguro y / o paralelo. por defecto, 3) e implementar algo como deshacer (viaje en el tiempo) requiere un trabajo cuidadoso (¡el programador imprescindible debe almacenar una receta para recuperar los valores anteriores!), Mientras que la programación funcional pura le compra todas estas cosas, y algunas más puedo he olvidado: "gratis".
Espero que esto no parezca fanatismo, solo quería agregar algo de perspectiva. La programación imperativa y especialmente la programación de paradigmas mixtos en lenguajes potentes como C # 3.0 siguen siendo formas totalmente efectivas de hacer las cosas y no hay una bala de plata .
[1] ... excepto posiblemente con respecto al uso de memoria (cf. foldl y foldl 'en Haskell).
Para ampliar el comentario de Konrad:
y el orden de evaluación no está bien definido
Algunos lenguajes funcionales tienen lo que se llama evaluación diferida. Lo que significa que una función no se ejecuta hasta que se necesita el valor. Hasta ese momento, la función en sí es lo que se pasa.
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 en ese momento. En una evaluación perezosa, diría que agrega 2 + 2, pero si el resultado nunca se usa, nunca se suma.
Si tiene la oportunidad, recomendaría obtener una copia de Lisp / Scheme y hacer algunos proyectos en él. La mayoría de las ideas que últimamente se han convertido en carretas se expresaron en Lisp hace décadas: programación funcional, continuaciones (como cierres), recolección de basura, incluso XML.
Entonces, esa sería una buena manera de comenzar con 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.
@ Creighton:
En Haskell hay una función de biblioteca llamada producto :
prouduct list = foldr 1 (*) list
o simplemente:
product = foldr 1 (*)
entonces el factorial "idiomático"
fac n = foldr 1 (*) [1..n]
simplemente sería
fac n = product [1..n]
La programación procesal divide 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 "programación funcional" como sin efectos secundarios, que es bastante diferente pero irrelevante para todos los lenguajes funcionales principales, excepto Haskell.
Para comprender la diferencia, uno debe comprender que el paradigma "el padrino" de la programación procesal y funcional es la programación imperativa .
Básicamente, la programación procesal es simplemente una forma de estructurar programas imperativos en los que el método principal 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, convirtiéndose en un objeto con un "estado actual", además de que este objeto tiene un conjunto de funciones, métodos y otras cosas que le permiten programador manipular o actualizar el estado.
Ahora, en lo que respecta a la programación funcional, la esencia de su enfoque es que identifica qué valores tomar y cómo deben transferirse estos valores. (por lo que no hay estado ni datos mutables, ya que toma funciones como valores de primera clase y los pasa como parámetros a otras funciones).
PD: comprender cada paradigma de programación para el que se debe 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: esta respuesta quora tiene una gran explicación.
Ninguna de las respuestas aquí muestra programación funcional idiomática. La respuesta factorial recursiva es excelente para representar la recursividad en FP, pero la mayoría del código no es recursiva, por lo que no creo que esa respuesta sea totalmente representativa.
Digamos que tiene una serie de cadenas, y cada cadena representa un número entero como "5" o "-200". Desea verificar este conjunto de cadenas de entrada con su caso de prueba interno (Uso de la comparación de enteros). Ambas soluciones se muestran a continuación.
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;
}
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 funcionales puros son generalmente lenguajes de investigación (como al mundo real le gustan los efectos secundarios gratuitos), los lenguajes de procedimiento del mundo real utilizarán la sintaxis funcional mucho más simple cuando sea apropiado.
Esto generalmente se implementa con una biblioteca externa como Lodash , o está disponible integrado 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 tres últimos de los cuales se puede consultar para mayor comprensión.
Para ser utilizado en la naturaleza, el compilador normalmente tendrá que averiguar cómo convertir internamente la versión funcional en la versión de procedimiento, ya que la sobrecarga de llamadas a funciones es demasiado alta. Los casos recursivos como el factorial que se muestra utilizará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 && ret
optimización incluso cuando .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 generalmente no es una preocupación con el desarrollo web). Los lenguajes como Rust se optimizarán internamente (y tienen funciones como try_fold
ayudar a la && ret
optimización).