¿Idiomas con una clara distinción entre subrutinas que son puramente funcionales, mutantes, cambiantes de estado, etc.?

8

Últimamente me he sentido cada vez más frustrado porque en la mayoría de los lenguajes de programación modernos con los que he trabajado (C / C ++, C #, F #, Ruby, Python, JS y más) hay muy poco, si es que hay alguno, soporte de lenguaje para determinar qué una subrutina realmente servirá.

Considere el siguiente pseudocódigo simple:

var x = DoSomethingWith(y);

¿Cómo determino qué hará realmente la llamada a DoSomethingWith (y) ? ¿Mutará y , o devolverá una copia de y ? ¿Depende del estado global o local, o solo depende de y ? ¿Cambiará el estado global o local? ¿Cómo afecta el cierre el resultado de la llamada?

En todos los idiomas que he encontrado, casi ninguna de estas preguntas se puede responder simplemente mirando la firma de la subrutina, y casi nunca hay soporte en tiempo de compilación o tiempo de ejecución. Por lo general, la única forma es confiar en el autor de la API y esperar que la documentación y / o las convenciones de nomenclatura revelen lo que realmente hará la subrutina.

Mi pregunta es la siguiente: ¿Existe algún lenguaje hoy en día que haga distinciones simbólicas entre este tipo de escenarios y ponga restricciones de tiempo de compilación sobre qué código puede escribir realmente?

(Por supuesto, hay algo de apoyo para esto en la mayoría de los lenguajes modernos, como diferentes niveles de alcance y cierre, la separación entre código estático y de instancia, funciones lambda, etc.) Pero con demasiada frecuencia estos parecen entrar en conflicto entre sí. Por ejemplo, una función lambda generalmente será puramente funcional y simplemente devolverá un valor basado en parámetros de entrada, o mutará los parámetros de entrada de alguna manera, pero generalmente es posible acceder a variables estáticas desde una función lambda, que a su vez puede le da acceso a las variables de instancia, y luego todo se separa).

Christian Palmstierna
fuente
1
Tenga en cuenta que "puramente funcional" es ambiguo. Probablemente quiere decir "puro" (libre de efectos secundarios); "funcional" implica un paradigma de programación que trata las funciones como objetos de primera clase y permite funciones de orden superior. Esas funciones no necesariamente tienen que ser puras, y la mayoría de los lenguajes de programación funcionales permiten funciones impuras.
tdammers
Eso suena como una distinción semántica importante para hacer. Lo que quiero decir con funcional es en el sentido matemático, es decir, que la rutina solo depende de los datos de entrada, y no lee ni escribe en ningún otro dato del programa. ¿Es "función pura" un término más correcto para describir esto?
Christian Palmstierna

Respuestas:

10

Sí, quieres mirar a Haskell. Hace exactamente lo que quieres. Todas las funciones son puras por defecto y solo pueden mutar el estado usando Monads. También Haskell tiene garantías estáticas muy fuertes sobre todo tipo de cosas http://learnyouahaskell.com/

Zachary K
fuente
Ah! He escuchado muchas cosas buenas sobre Haskell, pero no he podido investigarlo profundamente. Tal vez se ajuste a mi factura :)
Christian Palmstierna
Parece que es donde suceden muchas cosas interesantes
Zachary K
55
Las mónadas no pueden mutar el estado más que otros tipos de datos; están sujetos a las mismas restricciones de pureza que el resto del lenguaje. Después de todo, no tienen nada de especial, aparte de un poco de azúcar de sintaxis. Lo que pueden hacer es proporcionar una abstracción conveniente sobre el código que acumula el estado a través de llamadas a funciones encadenadas o recursivas, y cuando se compila contra el tiempo de ejecución, dicho código se reduce a un código imperativo similar al código monádico. Sin embargo, sigue siendo una abstracción, y el código sigue siendo 100% puro.
tdammers
44
Además, las funciones no son puras por defecto, son puras, punto. Haskell no puede expresar funciones impuras.
tdammers
3
@CPX: Existen al menos tres marcos web de fortaleza industrial para Haskell (Yesod, Happstack y Snap), y una gran cantidad de bibliotecas para prácticamente todas las tareas que menciona (aunque no estoy seguro acerca de SOAP); La calidad promedio del código dentro del ecosistema Haskell tiende a ser excelente. El mayor problema que puedo prever es encontrar suficientes programadores para soportar tal cosa durante un período de mantenimiento más largo.
tdammers
6

C y C ++ tienen soporte muy limitado para al menos parte del problema a través de la constpalabra clave; Si bien esto por sí solo no controla la pureza, se puede usar (especialmente en C ++) para decirle al compilador que una estructura de datos en particular no se debe modificar a través de un puntero dado. Algunos compiladores (p. Ej., Gcc) también proporcionan un atributo "puro" como una extensión de lenguaje para imponer plenamente la pureza. (Ver esta pregunta para más detalles).

El lenguaje de programación D tiene soporte para declarar explícitamente la pureza de las funciones, y los compiladores verificarán y aplicarán la pureza (es decir, tratar de llamar a una función no pura desde una función pura produce un error del compilador).

Haskell es completamente puro, es decir, el lenguaje en sí no puede expresar funciones impuras, y no existe el concepto de "rutina". Cualquier cosa que no pueda resolverse utilizando solo funciones puras se subcontrata al tiempo de ejecución (impuro); Un programa con efectos secundarios se implementa construyendo (utilizando construcciones exclusivamente puras) una estructura de datos diferida que describe el comportamiento del programa y luego entregándolo al tiempo de ejecución. La comunidad de Haskell está experimentando activamente con todo un zoológico de lenguajes de programación, algunos de ellos puros, otros con pureza explícita.

Puede haber más, pero estas son las que conozco.

tdammers
fuente
gcc admite declaraciones explícitas de pureza como una extensión como esta
inútil el
Las funciones miembro const de C ++ controlan la pureza, si sus clases no tienen funciones miembro no const. Todos los datos se vuelven constantes, y luego todo es puramente funcional.
tp1
@ tp1: No del todo. Una función miembro const puede producir fácilmente efectos secundarios al llamar a métodos estáticos no constantes de otras clases, crear instancias de otras clases o simplemente llamar a funciones libres o modificar variables globales. Solo funciona si bloquea toda su base de código en all-const, incluidas todas las bibliotecas que usa (incluso STL).
tdammers
@tdammers: el bloqueo total no es muy malo ya que los datos pueden inicializarse a diferentes valores mediante parámetros del constructor. Pero requiere que todos sus datos estén ubicados dentro de sus clases y no en variables globales.
tp1
@ tp1: También es muy poco práctico, ya que reduce la cantidad de bibliotecas que puede usar de manera segura a prácticamente cero.
tdammers
1

Parece que Felix tiene tres categorías:

  1. funciones

    There is a rule for functions: 
    
    A function introduced by a fun binder is not allowed to have any side effects.
    
    The compiler does not enforce this rule, but it does take advantage of it when
    optimising your code. 
    
  2. procedimientos

    Procedures do not return a value, and may and generally should have side-effects.
    
  3. generadores

    A generator is a function that may have side effects.
    
yakiv
fuente
Esto suena como un lenguaje que intenta hacer exactamente lo que busco. Sin embargo, es lamentable que las funciones no se apliquen a los compiladores (después de algunos años trabajando profesionalmente, he desarrollado una desconfianza instintiva para las bibliotecas de otros programadores ;-)). Además, dado que es tan nuevo, podría jugar con él, pero no parece un candidato para el desarrollo comercial / profesional :(
Christian Palmstierna
1
@ChristianPalmstierna: Me topé con la pregunta de este año. Desafortunadamente, como tantas propiedades interesantes, decidir si una función es pura, es equivalente a resolver el problema de detención. Tenga en cuenta que esto no significa necesariamente que no pueda hacerlo, solo significa que hay infinitas funciones puras cuya pureza el compilador no puede probar y, por lo tanto, debe rechazar como impuro aunque no lo sean. (Sin embargo, esto no es diferente de la comprobación de tipos estática. Por lo general, hay infinitos programas que son seguros, pero no se puede demostrar que son seguros. No nos impide escribir).
Jörg W Mittag