¿Por qué se desaconseja el uso de operadores de asignación o bucles en la programación funcional?

9

Si mi función cumple con dos requisitos por debajo, creo que la función Sum devuelve la suma de los elementos en una lista en la que el elemento se evalúa como verdadero para la condición dada califica para ser referida como función pura, ¿no es así?

1) Para un conjunto dado de i / p, se devuelve la misma o / p independientemente del tiempo cuando se llama a la función

2) No tiene ningún efecto secundario

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

Ejemplo: Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

La razón por la que hago esta pregunta es porque veo casi todas las personas que aconsejan evitar el operador de asignación y los bucles porque es un estilo de programación imperativo. Entonces, ¿qué puede salir mal con el ejemplo anterior que utiliza bucles y operadores de asignación en el contexto de la programación de funciones?

rahulaga_dev
fuente
1
No tiene ningún efecto secundario , tiene efectos secundarios, cuando la itemvariable muta en el bucle.
Fabio
@Fabio ok. Pero, ¿puedes explicar el alcance del efecto secundario?
rahulaga_dev

Respuestas:

16

¿Qué hay en la programación funcional que hace la diferencia?

La programación funcional es por principio declarativa . Dices cuál es tu resultado en lugar de cómo calcularlo.

Echemos un vistazo a la implementación realmente funcional de su fragmento. En Haskell sería:

predsum pred numbers = sum (filter pred numbers)

¿Está claro cuál es el resultado? Entonces, es la suma de los números que se encuentran con el predicado. ¿Cómo se calcula? No me importa, pregunta al compilador.

Posiblemente podría decir que usar sumy filteres un truco y no cuenta. Deje que se implemente sin estos ayudantes (aunque la mejor forma sería implementarlos primero)

La solución "Functional Programming 101" que no utiliza sumes con recursividad:

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

Todavía está bastante claro cuál es el resultado en términos de llamada de función única. Es 0, o recursive call + h or 0, dependiendo de pred h. Sigue siendo bastante sencillo, incluso si el resultado final no es inmediatamente obvio (aunque con un poco de práctica, esto realmente se lee como un forbucle).

Compare eso con su versión:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

Cual es el resultado? Oh, ya veo: una sola returndeclaración, sin sorpresas aquí: return result.

Pero que es result? int result = 0? No parece correcto Haces algo más tarde con eso 0. Ok, le agregas items. Y así.

Por supuesto, para la mayoría de los programadores, es bastante obvio lo que sucede en una función simple como esta, pero agregue una returndeclaración adicional o algo así y de repente se vuelve más difícil de rastrear. Todo el código trata sobre cómo y qué queda para que el lector descubra: este es claramente un estilo muy imperativo .

Entonces, ¿las variables y los bucles están mal?

No.

Hay muchas cosas que son mucho más fáciles de explicar por ellos, y muchos algoritmos que requieren que el estado mutable sea rápido. Pero las variables son inherentemente imperativas, explicando cómo en lugar de qué , y dando poca predicción de cuál puede ser su valor unas pocas líneas más tarde o después de algunas iteraciones de bucle. Los bucles generalmente requieren que el estado tenga sentido, por lo que también son inherentemente imperativos.

Las variables y los bucles simplemente no son programación funcional.

Resumen

La programación funcional contemporánea es un poco más de estilo y una forma útil de pensar que un paradigma. Esta mentalidad tiene una fuerte preferencia por las funciones puras, pero en realidad es solo una pequeña parte.

Los lenguajes más extendidos le permiten usar algunas construcciones funcionales. Por ejemplo, en Python puede elegir entre:

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

o

return sum(filter(pred, numbers))

o

return sum(n for n in numbers if pred(n))

Estas expresiones funcionales se ajustan bien a ese tipo de problemas y simplemente acortan el código (y más corto es bueno ). No debe reemplazar irremediablemente el código imperativo con ellos, pero cuando encajan, casi siempre son una mejor opción.

Frax
fuente
Gracias por una buena explicación!
rahulaga_dev
1
@RahulAgarwal Puede encontrar esta respuesta interesante, captura muy bien un concepto tangencial de establecer verdades versus describir los pasos. También me gusta la frase "los lenguajes declarativos contienen efectos secundarios, mientras que los lenguajes imperativos no lo tienen", por lo general, los programas funcionales tienen cortes limpios y muy visibles entre el código con estado que trata con el mundo exterior (o ejecutan algún algoritmo optimizado) y el código puramente funcional.
Frax
1
@Frax: gracias !! Lo investigaré. También recientemente encontré una charla de Rich Hickey sobre el valor de los valores. Es realmente brillante. Creo que una regla
básica
1
@Frax: También es justo decir que FP es una abstracción sobre la programación imperativa, porque en última instancia alguien tiene que instruir a la máquina sobre "cómo hacerlo", ¿verdad? En caso afirmativo, ¿no es la programación imprescindible tener más control de bajo nivel en comparación con FP?
rahulaga_dev
1
@Frax: Estoy de acuerdo con Rahul en que el imperativo es un nivel inferior en el sentido de que está más cerca de la máquina subyacente. Si el hardware pudiera hacer copias de datos sin costo, no necesitaríamos actualizaciones destructivas para mejorar la eficiencia. En este sentido, el paradigma imperativo está más cerca del metal.
Giorgio
9

El uso del estado mutable generalmente se desaconseja en la programación funcional. Los bucles se desaconsejan como consecuencia, porque los bucles solo son útiles en combinación con el estado mutable.

La función en su conjunto es pura, lo cual es excelente, pero el paradigma de la programación funcional no solo se aplica a nivel de funciones completas. También desea evitar el estado mutable también a nivel local, dentro de las funciones. Y el razonamiento es básicamente el mismo: evitar el estado mutable hace que el código sea más fácil de entender y evita ciertos errores.

En su caso, podría escribir, numbers.Where(predicate).Sum()que es claramente mucho más simple. Y más simple significa menos errores.

JacquesB
fuente
gracias !! Creo que me faltaba una línea llamativa, pero el paradigma de la programación funcional no solo se aplica a nivel de funciones completas, sino que ahora también me pregunto cómo visualizar este límite. Básicamente, desde la perspectiva del consumidor, es pura función, pero ¿el desarrollador que realmente escribió esta función no ha seguido las pautas de la función pura? confundido :(
rahulaga_dev
@RahulAgarwal: ¿Qué límite?
JacquesB
¿Estoy confundido en sentido si el paradigma de programación califica para ser como FP desde la perspectiva del consumidor de funciones? Bcoz si miro la implementación de implementación de Wherein numbers.Where(predicate).Sum(), hace uso de foreachloop.
rahulaga_dev
3
@RahulAgarwal: Como consumidor de una función, realmente no le importa si una función o módulo usa internamente estado mutable siempre que sea externamente puro.
JacquesB
7

Si bien tiene razón en que desde el punto de vista de un observador externo, su Sumfunción es pura, la implementación interna claramente no es pura: tiene un estado almacenado en el resultque muta repetidamente. Una de las razones para evitar el estado mutable es porque produce una mayor carga cognitiva en el programador, lo que a su vez conduce a más errores [cita requerida] .

Si bien en un ejemplo simple como este, la cantidad de estado mutable que se almacena es probablemente lo suficientemente pequeña como para que no cause problemas graves, el principio general aún se aplica. Un ejemplo de juguete como Sumprobablemente no sea la mejor manera de ilustrar la ventaja de la programación funcional sobre el imperativo: intente hacer algo con mucho estado mutable y las ventajas pueden volverse más claras.

Philip Kendall
fuente