La programación automática es un paradigma de programación , al utilizar un programa o su fragmento como modelo de algún autómata formal . También se conoce otro "paradigma de la programación automática, que consiste en representar entidades de comportamiento complejo en forma de objetos de control automatizados, cada uno de los cuales es un objeto de control y un autómata". Al mismo tiempo, se propone pensar en un programa, como en el control automático, como un sistema de objetos de control automatizado.
Dependiendo de la tarea específica en la programación automática, se pueden usar tanto autómatas finitos como autómatas con una estructura más compleja.
Las siguientes características son decisivas para la programación automática:
La ejecución completa del código de estilo autómata es un bucle (posiblemente implícito) de pasos de autómatas.
El nombre de programación automática también se justifica por el hecho de que el estilo de pensamiento (percepción del proceso de ejecución) cuando se programa en esta técnica reproduce casi exactamente el estilo de pensamiento cuando se compilan autómatas formales (como una máquina de Turing , una máquina de Markov , etc. )
Suponga, por ejemplo, que desea escribir un programa en C que lea texto del flujo de entrada estándar, que consiste en líneas, y para cada línea imprima la primera palabra de esta línea y el salto de línea. Es claro que para esto, mientras lee cada línea, primero debe omitir los espacios, si los hubiere, al principio de la línea; luego lea las letras que componen la palabra e imprímalas hasta que la palabra termine (es decir, la línea termina o se encuentra un espacio en blanco); finalmente, cuando la primera palabra ha sido leída e impresa con éxito, es necesario leer la línea hasta el final sin imprimir nada. Habiendo encontrado (en cualquier fase) un carácter de nueva línea, debe imprimir una nueva línea y continuar desde el principio. Si (nuevamente, en cualquier fase) ocurre la situación de "fin de archivo", se debe detener el trabajo.
Un programa que resuelve este problema en el estilo imperativo tradicional podría verse así ( lenguaje C ):
#incluir <stdio.h> int principal () { intc ; _ hacer { hacer c = obtener char (); mientras ( c == ' ' ); while ( c != ' ' && c != '\n' && c != EOF ) { poner ( c ); c = obtener char (); } poner ( '\n' ); mientras que ( c != '\n' && c != EOF ) c = obtener char (); } mientras ( c != EOF ); devolver 0 ; }El mismo problema se puede resolver pensando en términos de autómatas finitos. Tenga en cuenta que el análisis de una cadena se divide en tres fases: omitir los espacios iniciales, imprimir una palabra y omitir el resto de la cadena. Llamemos a estas tres fases estados before , insidey after. El programa ahora podría verse así:
#incluir <stdio.h> int principal () { enum declara { antes , dentro , después } estado ; intc ; _ estado = antes ; while (( c = getchar ()) != EOF ) { cambiar ( estado ) { caso antes : si ( c == '\n' ) { poner ( '\n' ); } más si ( c != ' ' ) { poner ( c ); estado = dentro ; } romper ; caso interior : cambiar ( c ) { caso ' ' : estado = después ; romper ; caso '\n' : poner ( '\n' ); estado = antes ; romper ; predeterminado : poner ( c ); } romper ; caso después de : si ( c == '\n' ) { poner ( '\n' ); estado = antes ; } } } devolver 0 ; }o así:
#incluir <stdio.h> vacío estático ( * estado ) ( int ); vacío estático antes ( int c ); vacío estático dentro ( int c ); vacío estático después ( int c ); anular antes ( int c ) { si ( c == '\n' ) { poner ( '\n' ); } más si ( c != ' ' ) { poner ( c ); estado = & dentro ; } } vacío dentro ( int c ) { cambiar ( c ) { caso ' ' : estado = & después ; romper ; caso '\n' : poner ( '\n' ); estado = & antes ; romper ; predeterminado : poner ( c ); } } vacío después de ( int c ) { si ( c == '\n' ) { poner ( '\n' ); estado = & antes ; } } int principal () { intc ; _ estado = & antes ; while (( c = getchar ()) != EOF ) { ( * estado )( c ); } devolver 0 ; }A pesar de que el código obviamente se ha vuelto más largo, tiene una ventaja indudable: la lectura (es decir, llamar a una función getchar()) ahora se realiza exactamente en un lugar . Además, cabe señalar que en lugar de los cuatro bucles utilizados en la versión anterior, ahora solo se utiliza un bucle. El cuerpo del ciclo (a excepción de las acciones realizadas en la cabecera) es un paso del autómata , mientras que el propio ciclo marca el ciclo del autómata .
El programa implementa (simula) el funcionamiento de la máquina de estados finitos que se muestra en la figura. La letra N en el diagrama indica el carácter de fin de línea, la letra S indica el carácter de espacio y la letra A indica todos los demás caracteres. En un paso, el autómata hace exactamente una transición, según el estado actual y el carácter leído. Algunas transiciones van seguidas de una impresión del carácter leído; dichas transiciones están marcadas con asteriscos en el diagrama.
En términos generales, no es necesario observar estrictamente la división del código en controladores de estados separados. Además, en algunos casos, el propio concepto de estado puede estar formado por los valores de varias variables, por lo que será casi imposible tener en cuenta todas las combinaciones posibles de ellas. En este ejemplo, puede ahorrar una gran cantidad de código si observa que las acciones realizadas en el carácter de "fin de línea" son independientes del estado. Un programa equivalente al anterior, pero escrito con este comentario en mente, se verá así:
#incluir <stdio.h> int principal () { enum declara { antes , dentro , después } estado ; intc ; _ estado = antes ; while (( c = getchar ()) != EOF ) { si ( c == '\n' ) { poner ( '\n' ); estado = antes ; continuar ; } cambiar ( estado ) { caso antes : si ( c != ' ' ) { poner ( c );