¿La programación funcional no es adecuada para sistemas embebidos debido al uso extensivo de la recursividad?

No.

La recursión no siempre tiene que usar la pila. Hay dos formas generales de evitar esto: en una configuración estricta, la eliminación de llamadas de cola no usa la pila, y en una configuración perezosa podemos reificar nuestro cálculo como una estructura de datos perezosa que usa el montón.

Al igual que podemos reescribir cualquier procedimiento recursivo con un bucle y una estructura de datos explícita basada en el montón para el almacenamiento en un lenguaje imperativo, siempre podemos reescribir una función recursiva al estilo de paso de continuación para evitar completamente el uso de la pila. Esto significa que podemos tener el mismo tipo de control sobre el uso del montón y la pila en un programa funcional con recursividad que en un programa imperativo con bucles. Por supuesto, esto requiere llamadas de cola adecuadas, ¡pero eso es algo que todo lenguaje funcional razonable tiene!

Además, toda la separación de pila / montón es solo un detalle de implementación. ¡Nada dice que cada lenguaje debe tener una pila y un montón como C! Un lenguaje funcional siempre podría implementar la pila de una manera diferente o simplemente hacerla más grande.

El uso de la pila no es el problema. Pero el uso de memoria es, al menos para algunas aplicaciones.

Cuando los lenguajes funcionales tienen problemas de memoria, el problema no es la recurrencia o incluso el uso de la pila en general. Más bien, el problema es doble: control limitado sobre la asignación y la recolección de basura . Los lenguajes funcionales modernos naturalmente asignan muchos valores en rápida sucesión y, por lo tanto, requieren un recolector de basura particularmente activo ajustado para este caso de uso. Desafortunadamente, el rendimiento de este tipo de recolector de basura es difícil de predecir. (No estoy seguro de si es mucho peor que los GC de lenguajes como Java).

Esta dependencia de la recolección de basura y el control limitado sobre la memoria hacen que la mayoría de los lenguajes funcionales modernos se adapten mal a ciertas aplicaciones integradas, especialmente a aquellas con poca memoria o limitaciones de tiempo real.

Sin embargo, son una opción perfectamente razonable para otras aplicaciones integradas. Algunos chips incrustados, por ejemplo, ejecutan Java o incluso lenguajes recolectados de basura menos eficientes; en estos casos, usar OCaml o incluso Haskell no debería ser un gran problema. Requeriría un poco más de disciplina que la programación OCaml / Haskell normal, pero eso también es cierto para los lenguajes imperativos en estos sistemas.

Por lo tanto, los lenguajes funcionales encajan mal en ciertos sistemas embebidos, no debido a la recurrencia, sino al comportamiento de la memoria que es más difícil de predecir, en gran parte gracias a la recolección de basura.

Elixir tiene una comunidad activa que realiza aplicaciones integradas utilizando el marco de Nervios. He incursionado en él y descubrí que es una gran solución para soluciones integradas casi en tiempo real.

El software Elixir se ejecuta en Erlang VM, que está optimizado para software en tiempo real. Es compatible con la recursividad de cola que resuelve los problemas de crecimiento de la pila. También tiene GC por proceso, por lo que la VM no se bloquea cuando un proceso hace GC, solo el proceso único.

Entonces, la respuesta corta es que la programación funcional se puede usar para sistemas embebidos. Probablemente necesitará un poco más de potencia de CPU que una implementación C equivalente, pero los beneficios de usar un lenguaje de nivel superior pueden compensar esto.

El marco de Nervios viene con soporte para algunos de los tableros integrados populares como RPi y BBB. Viene con su propia distribución de Linux, configuración de kernel y compilaciones cruzadas para las plataformas integradas compatibles.

Me tomó menos de un día aprender los conceptos básicos de Nervios, el BBB (nunca he usado uno antes) y tener un programa simple para alternar y ejecutar patrones de persecución en el BBB. Esto incluía la capacidad de actualizar nuevas cargas de firmware a través de http en mi red local.

Los lenguajes funcionales no son inherentemente malos para escribir programas de sistemas de bajo nivel.

Por un lado, la eliminación de la cola de llamadas resuelve el problema de memoria que ves con la recursividad.

Segundo, el verdadero problema es que muchos tienen recolección de basura. * Cualquier * lenguaje que tenga recolección automática de basura es generalmente inapropiado para la programación de sistemas de bajo nivel, ya sea Java, Haskell, Ruby, JavaScript o Go.

Tercero, los lenguajes funcionales pueden tener características que los hacen bastante buenos para la programación de sistemas, ya que son más modulares, expresivos, fáciles de abstraer, correctos, susceptibles de ser concurrentes y seguros que C, el lenguaje de facto de los sistemas, pero en sin costo adicional Basta con mirar el hábito [1] (que lamentablemente está en hibernación).

[1] http://hasp.cs.pdx.edu/habit-rep

No, porque casi siempre es la recursión de la cola, que no necesita usar la pila.

Es más la mentalidad: la programación integrada tiene que ver con el estado de encadenamiento. Estás modelando un mundo cambiante y tienes un conjunto cambiante de tareas que hacer. Creo que está implícito que la Programación funcional es que la mayoría de los datos están disponibles antes de que comience el programa: está realizando transformaciones en o extrayendo datos de una base de datos existente. En programación incrustada. Todos los datos llegan después del inicio del programa, en orden y cantidad impredecibles.

El sistema integrado no implica pequeño, ni implica tiempo real. Integrado solo significa que es un sistema informático dedicado a alguna funcionalidad. Sí, podría ser un controlador que ejecuta la bomba de combustible para una turbina de gas, pero también podría ser la pequeña caja x86 ITX que ejecuta Flash para la señalización digital en el Starbucks local.

Los lenguajes funcionales tienden a tener un comportamiento de tiempo de ejecución no determinista para el uso de memoria y la fluctuación de fase. Sin embargo, todo lo que significa es que si su hardware incrustado es lo suficientemente grande y se pueden satisfacer los límites de jitter, entonces Haskell (o Javascript como en Node.js) funcionará bien.

Hay muchas buenas respuestas aquí. Me gustaría detectar un error significativo en su pregunta, que parece haberse perdido.

La programación funcional no usa la recursión ampliamente. Con eso no quiero decir que la recursividad esté optimizada por TCO. Quiero decir que no se usa tanto como parece pensar la gente.

Cuando las personas aprenden programación funcional, se les enseña la recursividad explícita. A medida que se vuelven más hábiles, aprenden a dejar de usarlo.

Las bibliotecas de lenguajes funcionales proporcionan funciones que eliminan la placa repetitiva de las soluciones recursivas para que solo tenga que proporcionar el código específico que se repite. Por ejemplo, mientras un alumno en Haskell podría escribir la función de longitud de esta manera:

  longitud [] = 0
 longitud (x: xs) = 1 + longitud xs

el codificador experimentado escribirá algo como esto

  longitud = foldl '(+ 1) 0

Los codificadores experimentados utilizan principalmente pliegues, escaneos, recorridos y similares, solo escriben funciones explícitamente recursivas donde la función de biblioteca es un ajuste incómodo o se necesita alguna condición final inusual. Dependiendo del idioma, la función de la biblioteca puede escribirse imperativamente bajo el capó o usar alguna otra abstracción que no ponga en peligro la pila.

Entonces, si bien la recursión estructural es generalizada, la recursión procesal no tanto.

Como otros han señalado, incluso cuando se utiliza la recursividad explícita, el compilador / intérprete generalmente la optimiza. Scala, por ejemplo, generalmente lo convierte en un ciclo while. Entonces, incluso la cantidad de recursión realmente utilizada en la programación de FP no es un obstáculo para el uso integrado. Si los lenguajes FP no son adecuados para el trabajo incrustado típico (y algunos de ellos sinceramente no lo son, tal como están las cosas), la razón está en otra parte.

Forth es básicamente un FP, y se ha utilizado en entornos integrados durante mucho tiempo.

En incrustado, debe tener cuidado con el código que escribe. Esto es lo mismo para C como lo es para Forth, o cualquier otra cosa. No hay una red de seguridad que le dé un error de falla seg y un volcado de núcleo.

Pues sí y tal vez no. El uso de la recursividad al bucle es relativamente fácil de detectar por un compilador, y puede reescribir el bucle para iterar en lugar de recurrir. Sin embargo, eso solo es cierto para usos muy simples de la recursividad. Probablemente no desee que ocurra mucha recursividad si tiene un espacio de pila limitado. Es una decisión de diseño que debe tomar, los beneficios frente a los costos.

Muchas veces no puedes tomar esa decisión. Las reglas para sistemas embebidos confiables, como las reglas MISRA, específicamente prohíben la recursividad y la asignación de almacenamiento dinámico. Si no te importan tus P y Q, terminas como ese automóvil Toyota que se volvió loco y mató a un agente inmobiliario. No es que eso sea malo.

Hay formas de evitar el desbordamiento de la pila, pero la mayoría de los sistemas integrados solo proporcionan un compilador de C. Por lo tanto, debido a la falta de soporte de la cadena de herramientas, sí, la programación funcional normalmente no es adecuada para los sistemas integrados.