¿Tener variables locales mutables en una función que solo se usan internamente (por ejemplo, la función no tiene efectos secundarios, al menos no intencionalmente) todavía se considera "no funcional"?
Por ejemplo, en la verificación de estilo del curso "Programación funcional con Scala" se considera que cualquier var
uso es malo
Mi pregunta, si la función no tiene efectos secundarios, ¿todavía se desaconseja escribir código de estilo imperativo?
Por ejemplo, en lugar de utilizar la recursión de cola con el patrón del acumulador, ¿qué hay de malo en hacer un bucle local for y crear un mutable localListBuffer
y agregarlo, siempre que la entrada no cambie?
Si la respuesta es "sí, siempre se desaniman, incluso si no hay efectos secundarios", ¿cuál es la razón?
fuente
var
siempre es no funcional. Scala tiene vals perezosos y optimización de recursión de cola, lo que permite evitar los vars por completo.Respuestas:
Lo único que es inequívocamente una mala práctica aquí es afirmar que algo es una función pura cuando no lo es.
Si las variables mutables se usan de una manera que es verdaderamente y completamente autónoma, la función es externamente pura y todos están felices. De hecho, Haskell respalda esto explícitamente , con el sistema de tipos incluso asegurando que las referencias mutables no puedan usarse fuera de la función que las crea.
Dicho esto, creo que hablar de "efectos secundarios" no es la mejor manera de verlo (y es por eso que dije "puro" arriba). Cualquier cosa que cree una dependencia entre la función y el estado externo hace que las cosas sean más difíciles de razonar, y eso incluye cosas como saber la hora actual o usar el estado mutable oculto de una manera no segura para subprocesos.
fuente
El problema no es la mutabilidad per se, es una falta de transparencia referencial.
Una cosa referencialmente transparente y una referencia a ella siempre deben ser iguales, por lo que una función referencialmente transparente siempre devolverá los mismos resultados para un conjunto dado de entradas y una "variable" referencialmente transparente es realmente un valor en lugar de una variable, ya que no puede cambiar Puede hacer una función referencialmente transparente que tenga una variable mutable dentro; Eso no es un problema. Sin embargo, puede ser más difícil garantizar que la función sea referencialmente transparente, dependiendo de lo que esté haciendo.
Se me ocurre una instancia en la que la mutabilidad tiene que usarse para hacer algo que es muy funcional: la memorización. Memoization es almacenar en caché los valores de una función, por lo que no es necesario volver a calcularlos; es referencialmente transparente, pero usa mutación.
Pero, en general, la transparencia referencial y la inmutabilidad van juntas, aparte de una variable mutable local en una función y una memoria referencialmente transparentes, no estoy seguro de que haya otros ejemplos en los que este no sea el caso.
fuente
No es realmente bueno reducir esto a "buenas prácticas" versus "malas prácticas". Scala admite valores mutables porque resuelven ciertos problemas mucho mejor que los valores inmutables, es decir, aquellos que son de naturaleza iterativa.
En perspectiva, estoy bastante seguro de que a través de
CanBuildFrom
casi todas las estructuras inmutables proporcionadas por scala, se realiza algún tipo de mutación internamente. El punto es que lo que exponen es inmutable. Mantener tantos valores inmutables como sea posible ayuda a que el programa sea más fácil de razonar y menos propenso a errores .Esto no significa que necesariamente deba evitar estructuras y valores mutables internamente cuando tenga un problema que se adapte mejor a la mutabilidad.
Con eso en mente, muchos problemas que generalmente requieren variables mutables (como el bucle) se pueden resolver mejor con muchas de las funciones de orden superior que proporcionan lenguajes como Scala (mapa / filtro / pliegue). Sé consciente de eso.
fuente
map
,filter
,foldLeft
YforEach
hacer el truco mayor parte del tiempo, pero no cuando lo hacen, ser capaz de sentir que soy "OK" para volver a la fuerza bruta código imperativo es agradable. (siempre y cuando no haya efectos secundarios, por supuesto)Además de los posibles problemas con la seguridad de los hilos, también suele perder mucha seguridad de tipo. Los bucles imperativos tienen un tipo de retorno
Unit
y pueden tomar casi cualquier expresión para las entradas. Las funciones de orden superior e incluso la recursividad tienen una semántica y tipos mucho más precisos.También tiene muchas más opciones para el procesamiento funcional de contenedores que con los bucles imperativos. Con imperativo, básicamente tienes
for
,while
y pequeñas variaciones en esos dos comodo...while
yforeach
.En funcional, tiene agregado, conteo, filtro, búsqueda, flatMap, fold, groupBy, lastIndexWhere, map, maxBy, minBy, partición, scan, sortBy, sortWith, span y takeWhile, solo para nombrar algunos más comunes de Scala's biblioteca estándar Cuando te acostumbras a tenerlos disponibles, los
for
bucles imperativos parecen demasiado básicos en comparación.La única razón real para usar la mutabilidad local es muy ocasionalmente para el rendimiento.
fuente
Yo diría que en su mayoría está bien. Además, generar estructuras de esta manera podría ser una buena manera de mejorar el rendimiento en algunos casos. Clojure ha abordado este problema proporcionando estructuras de datos transitorias .
La idea básica es permitir mutaciones locales en un alcance limitado y luego congelar la estructura antes de devolverla. De esta manera, su usuario aún puede razonar sobre su código como si fuera puro, pero puede realizar transformaciones en el lugar cuando lo necesite.
Como dice el enlace:
fuente
No tener variables locales mutables tiene una ventaja: hace que la función sea más amigable con los hilos.
Me quemé con una variable local (no en mi código, ni tenía la fuente) causando una corrupción de datos de baja probabilidad. La seguridad de subprocesos no se mencionó de una manera u otra, no hubo un estado que persistiera en las llamadas y no hubo efectos secundarios. No se me ocurrió que podría no ser seguro para subprocesos, perseguir una corrupción de datos aleatorios de 1 en 100,000 es un dolor real.
fuente