No es todos los dÃas que tienes que desarrollar software para un telescopio gigante. Sin embargo, cuando lo haces, expande tu comprensión, y apreciación, de cosas que antes dabas por sentado. En resumen, te obliga a experimentar. Descubrimos eso al desarrollar una herramienta de orquestación para el Telescopio de Treinta Metros, un telescopio que está siendo desarrollado por un consorcio de diferentes organizaciones y que se lanzará en 2030. ÷ÈÓ°Ö±²¥ estaba trabajando con el Instituto Indio de AstronomÃa (con sede en Bengaluru) y la Oficina del Proyecto TMT (con sede en Pasadena, CA.).
Ìý
La "experimentación" central a la que nos llevó un desafÃo en particular fue combinar Kotlin y Scala para desarrollar un lenguaje especÃfico del dominio. Entraremos en más detalles sobre cómo lo hicimos en breve, pero antes de llegar a eso, vale la pena describir la naturaleza del desafÃo.
Ìý
El desafÃo
Ìý
La herramienta de orquestación es una parte importante del telescopio utilizada por los cientÃficos para coordinar la compleja red de componentes del telescopio de diversas maneras según la observación que tengan la intención de realizar. En términos simples, es un motor de ejecución; los expertos en el dominio (es decir, los cientÃficos) deben ingresar la lógica requerida escribiendo scripts.
Ìý
El desafÃo fue que, si bien gran parte de la pila de software del telescopio se desarrolló en Scala, los cientÃficos que utilizarÃan la herramienta de orquestación y escribirÃan los scripts estaban familiarizados con lenguajes como C++. Usar Scala serÃa una curva de aprendizaje pronunciada.
Ìý
Si bien la mayorÃa de ellos tenÃan conocimientos sobre constructos básicos de programación y experiencia con lenguajes de script como Python, los principales desafÃos para ellos eran manejar conceptos y técnicas complejas como la multihilo y la concurrencia. Aunque los cientÃficos carecÃan de conocimientos sobre ellos, eran crÃticos para garantizar que la herramienta de orquestación pudiera ejecutar secuencias y configuraciones increÃblemente complejas.
Ìý
La solución fue intentar desarrollar un DSL integrado fácil de usar, que tuviera una curva de aprendizaje mÃnima y que se encargara de la concurrencia y la multihilo.
Ìý
Ahora que tienes el contexto del proyecto, te mostraremos cómo lo hicimos, comenzando con nuestro primer (y finalmente equivocado) intento con Scala.
Ìý
El primer intento: construir un DSL con Scala
Ìý
Inicialmente, elegimos Scala para implementar el DSL integrado y Sequencer como las API de Future y async/await del lenguaje eran efectivas para operaciones asÃncronas. Todas las modificaciones de datos dentro del script se realizaron dentro de las API de Future; estas se ejecutaron en un ejecutor de un solo hilo. Esto significaba que el script se ejecutaba de manera concurrente, asegurando que no hubiera posibilidad de acceder a los datos de manera paralela. Esto fue especialmente útil ya que eliminó problemas como las condiciones de carrera y la corrupción del estado debido al acceso paralelo.
Ìý
Un script simplista creado con nuestro DSL de Scala se muestra a continuación. El Script maneja un comando llamado 'observationPrep' programando un comando para un componente 'motor1' en un momento dado.
// TcsSync.scala
class TcsSync(csw: CswServices) extends Script(csw) {
Ìýprivate val timeKey = KeyType.TAITimeKey.make(name = "time")
ÌýhandleSetupCommand("observationPrep") { command =>
ÌýÌýÌýval executeAt = command(timeKey).head
ÌýÌýÌýspawn {Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý // --------------> [ 1 ]
ÌýÌýÌýÌýÌýcsw.scheduler.scheduleOnce(executeAt) {
ÌýÌýÌýÌýÌýÌýÌýspawn {Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý Ìý // --------------> [ 2 ]
ÌýÌýÌýÌýÌýÌýÌýÌýÌýval moveCommand =
ÌýÌýÌýÌýÌýÌýÌýÌýÌýÌýÌýSetup(tcs.prefix, CommandName("move30Degrees"), command.maybeObsId)
ÌýÌýÌýÌýÌýÌýÌýÌýÌýcsw.submit("motor1", moveCommand).awaitÌý Ìý Ìý Ìý Ìý // --------------> [ 3 ]
ÌýÌýÌýÌýÌýÌýÌý}
ÌýÌýÌýÌýÌý}
ÌýÌýÌýÌýÌýDone
ÌýÌýÌý}
Ìý}
}
AquÃ, spawn (en 1,2) es un alias para la función asÃncrona y .await (en 3) es un método de extensión para la función await. Scala proporciona las funciones async y await para simplificar el código asÃncrono.
Ìý
Las funciones asÃncronas marcan un bloque de código asÃncrono. El bloque suele contener una o más llamadas await, que marcan un punto donde la computación se suspenderá hasta que el Future esperado esté completo.
Ìý
En esta etapa, nosotros, como grupo de programadores, estábamos bastante satisfechos con el DSL. Sin embargo, no estábamos seguros de qué tan fácil serÃa de usar para las personas que realmente escribirÃan scripts para la herramienta de orquestación. Varios interesados también expresaron preocupaciones sobre la complejidad del DSL. Esto fue una clara señal de que necesitábamos una alternativa más fácil de usar.
Ìý
El problema con el DSL de Scala fue que los scripts requerÃan muchas operaciones asÃncronas y las API de Future de Scala exigÃan que los escritores de scripts ingresaran declaraciones async/await para cada una. Obviamente, esto añadió una complejidad significativa a la tarea del escritor de scripts. Olvidar escribir declaraciones await podrÃa dar lugar a un comportamiento no deseado.
Ìý
El ejemplo a continuación demuestra una instancia donde cada llamada a función es asÃncrona.
spawn {
Ìý
Ìýcsw.submit("motor1", moveCommand).await
Ìýcsw.publish(currentState()).await
Ìýcsw.submit("motor2",moveCommand).await
Ìýcsw.publish(currentState()).await
}
Ìý
El enfoque de Scala para la programación asÃncrona, que es asÃncrona por defecto y un comportamiento sincrónico explÃcito, complica el trabajo de los escritores de scripts. Además, debido a que los scripts tendrÃan que escribirse como una clase Scala, significaba que los escritores de scripts tendrÃan que aprender una construcción adicional, lo que podrÃa llevar a casos especÃficos. Por lo tanto, comenzamos la búsqueda de una mejor opción.
Ìý
En la segunda parte de esta serie de blogs, pasaremos a discutir cómo usamos Kotlin y por qué nos ayudó a resolver este desafÃo complicado.
Aviso legal: Las declaraciones y opiniones expresadas en este artÃculo son las del autor/a o autores y no reflejan necesariamente las posiciones de ÷ÈÓ°Ö±²¥.