¿Cuál es la diferencia entre programación procesal y programación funcional? [cerrado]

247

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?

Thomas Owens
fuente
Wikipedia implica que FP es un subconjunto de (es decir, siempre) programación declarativa, pero eso no es cierto y combina la taxonomía de IP frente a DP .
Shelby Moore III

Respuestas:

151

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.

Konrad Rudolph
fuente
99
Los valores inciertos como la entrada del usuario o los valores aleatorios son difíciles de modelar en lenguajes puramente funcionales, pero este es un problema resuelto. Ver mónadas.
Apocalisp
" pasos secuenciales , donde el programa funcional estaría anidado" significa proporcionar separación de preocupaciones enfatizando la composición de funciones , es decir, separando las dependencias entre las subcomputaciones de un cálculo determinista.
Shelby Moore III
esto parece incorrecto - los procedimientos también pueden estar anidados, los procedimientos pueden tener parámetros
Hurda
1
@Hurda Sí, podría haber redactado esto mejor. El punto es que la programación de procedimientos ocurre paso a paso en un orden predeterminado, mientras que los programas funcionales no se ejecutan paso a paso; más bien, los valores se calculan cuando se necesitan. Sin embargo, la falta de una definición generalmente aceptada de terminología de programación hace que tales generalizaciones sean casi inútiles. He modificado mi respuesta a ese respecto.
Konrad Rudolph
97

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 )

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 manera 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 generalmente no está definido.
  • 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 divide y vencerás.
  • Puede tener la característica de evaluación diferida.

Haskell

(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

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 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 --> UIntla firma en lugar de returns UIntdespués de ella.

(Puede salirse con el inicio del rango 2ya que el "operador" de multiplicación regresará 1cuando se le llame sin ningún argumento)

Brad Gilbert
fuente
Hola, ¿puede proporcionar un ejemplo para los siguientes 2 puntos mencionados para "Procedimiento" considerando el ejemplo de implementación factorial en Perl 6. 1) La salida de una rutina no siempre tiene una correlación directa con la entrada. 2) La ejecución de una rutina puede tener efectos secundarios.
Naga Kiran
sub postfix:<!> ($n) { [*] 1..$n }
Brad Gilbert, el
@BradGilbert -¿Puedes No operation can have side effectselaborarlo?
kushalvm
2
Probablemente la mejor respuesta que pude encontrar ... Y, investigué sobre esos puntos individuales ... ¡eso realmente me ayudó! :)
Navaneeth
1
@AkashBisariya 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 }
Brad Gilbert
70

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.

Omnimike
fuente
2
La mejor y más sucinta explicación que he visto en la web, ¡bravo!
Tommed
47

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.

juan
fuente
44
Si bien esta es la explicación que más me ha ayudado, todavía tengo dudas sobre el concepto de programación funcional. Estoy buscando un estilo de programación que no dependa de hacer referencia a objetos externos para ejecutarse (todo lo que la función debe ejecutarse debe pasarse como un parámetro). Por ejemplo, nunca pondría GetUserContext()en la función, se pasaría el contexto del usuario. ¿Es esta programación funcional? Gracias por adelantado.
Matt Cashatt
26

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

Aaron Hall
fuente
26

Programacion Funcional

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

Programación procesal

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 .

Hamza Zubair
fuente
55
Este ejemplo es realmente simple de entender el término "sin estado" y "datos inmutables" en la programación funcional, leer todas las definiciones y diferencias enumeradas anteriormente no despejó mi confusión hasta leer esta respuesta. ¡Gracias!
maximus
13

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í. :)

Herms
fuente
12

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.

C Hogg
fuente
10

Una programación funcional es idéntica a la programación de procedimientos en la que no se utilizan variables globales .

Nir O.
fuente
7

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.

Cuña
fuente
1
Después de leer esta página, estaba pensando en lo mismo -> "La recursión es un ejemplo clásico de programación de estilo funcional", y la borraste. Gracias, ahora creo que estoy obteniendo algo.
Mudassir Hussain
6

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).

Jared Updike
fuente
5

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.

Brian Leahy
fuente
4

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.

Mike Dunlavey
fuente
3

@ 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]
Jared Updike
fuente
Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación.
Nick Kitto
Creo que esto se publicó hace muchos años, antes de que se agregara el sistema de comentarios, si puede creerlo: stackoverflow.com/help/badges/30/beta?userid=2543
Jared Updike
2

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.

Jon Harrop
fuente
1

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.

Fouad Boukredine
fuente
0

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.

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 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.

Apéndice

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 && retoptimización incluso cuando .reducese 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_foldayudar a la && retoptimización).

Nicholas Pipitone
fuente