While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.Mientras actualizaba Semiconductor Toolkit, busqué una mejor manera de reconstruir la función Recuperar.
Abra la plataforma de distribución en JMP . Mire ese pequeño grupo de botones a la derecha, específicamente el botón "Recuperar". Está en todas partes. Casi todas las plataformas de JMP lo tienen. Está integrado en el ADN del lenguaje de diseño de JMP. Ahora piensa por un segundo en la frecuencia con la que ese pequeño botón te ha salvado el trasero en el fragor de tu batalla diaria con los datos. Mucho, ¿verdad? Probablemente sea una de las capacidades de JMP que más se da por sentada. Pero yo diría que es uno de los innumerables pequeños detalles que hacen que JMP sea tan útil.
Ahora, programadores de JSL, piensen por un segundo en replicar esa función de recuperación en un complemento personalizado. Es una tarea bastante avanzada: la forma en que he visto a la mayoría de la gente hacerlo (incluido yo mismo) implicaría crear una variable en un espacio de nombres personalizado o global para cada elemento de la GUI que desee recuperar. Luego están las funciones de soporte para recopilar la información de cada elemento de la GUI en tiempo de ejecución y las que escriben la información cuando se presiona el botón Recuperar.Él. Es. A. Real. Dolor.Configurar una capacidad de recuperación en scripts JMP y aplicaciones personalizadas es probablemente una de las tareas más laboriosas que existen. Para uno o dos componentes en una GUI, no es gran cosa, pero el proceso estándar no escala bien. Y, sin embargo, para ese esfuerzo, una capacidad de recuperación imparte un nivel de pulido y facilidad de uso a un complemento que lo eleva a un nuevo nivel de profesionalismo. A los usuarios les encanta cuando hay una capacidad de recuperación en un complemento. Y, para ser franco, odio codificarlo.
Podemos reconstruirlo
Permítanme retroceder un poco. La idea que analiza esta publicación comenzó a principios de 2016. Recién había comenzado a actualizar el Kit de herramientas para semiconductores (STK) a la versión 2. Por varias razones que cubriré en otra publicación de blog, decidí migrar todo el proyecto a Creador de aplicaciones en JMP .Esto significó muchos retoques con el código base y las GUI, ordenar las cosas, estandarizar el código siempre que fuera posible y agregar algunas capacidades nuevas aportadas por colegas (¡gracias de nuevo, muchachos!). Desafortunadamente, este trabajo rompió todo el código existente para Recall desde la versión 1. Ante la tarea de reconstruir el código de recuperación para todas las plataformas del complemento, pensé: “Tiene que haber otra forma de abordar este problema. Tiene que haber una manera de reducir toda esa codificación repetitiva”.
Casi al mismo tiempo, estaba estudiando cómo usar matrices asociativas (AA) en JSL para algunas otras capacidades en STK. No veo que la gente los use mucho, pero los AA pueden ser una herramienta increíblemente poderosa en su arsenal de secuencias de comandos. Por el bien de esta discusión, un AA es un tipo especial de lista. Puedes buscarlo, poner cosas, sacar cosas, etc. Lo que los hace especiales es que cada entrada de la lista tiene dos partes, una clave y un valor. Hay algunas publicaciones de blog y recursos excelentes sobre los AA ( Documentación JMP Sobre la conversión de una lista de matrices asociativas en una tabla de datos Consejo JSL: encontrar valores únicos en una lista ) al que hago referencia cuando trabajo con AA. Cubren gran parte del material sobre AA y tienen algunos ejemplos excelentes. Volviendo a mi problema original, se me ocurrió que el nombre de la variable global y su contenido probablemente podrían traducirse a un par clave-valor en un AA. Entonces, siendo mi musa del momento, me pregunté si podría usar una matriz asociativa para construir un mejor recuerdo.
Bien, para ayudar a que lo que estaba pensando tenga algún sentido, veamos cómo se hace esto normalmente. Hay dos operaciones básicas necesarias para implementar Recall. Cuando el usuario realiza alguna acción desencadenante, es decir, hace clic en "Aceptar", todos los elementos de la GUI, es decir, cuadros de lista, cuadros de radio, cuadros de texto, etc., que se van a recuperar deben extraer y almacenar su contenido de alguna manera. Luego, cuando el usuario hace clic en el botón "Recuperar", el contenido almacenado debe devolverse a los lugares apropiados en la GUI. En el centro de estas dos operaciones hay un par de mensajes JSL: << obtener() y << establecer() . Hay diferentes tipos de estos dependiendo del elemento GUI (no me preguntes por qué). – No escribí JSL – Solo uso esas cosas) pero básicamente son variantes de estos dos y hacen las mismas cosas. El índice de secuencias de comandos en JMP (Ayuda > Índice de secuencias de comandos) es una excelente manera de saber cuál va con qué elemento de la GUI.
Entonces, mi solución basada en AA tiene que hacer varias cosas. Primero, tengo que poder usar << obtener() y << establecer() en todas sus permutaciones. Tengo que poder decirle qué elementos de la GUI quiero guardar. Y tengo que tener un lugar para guardar esta información que no desaparezca cuando el complemento termine de ejecutarse. Esto significa que el flujo de trabajo revisado implicaría crear un AA utilizando una lista de los elementos de la GUI que deseo restaurar. Necesitaría poner este AA en el espacio de nombres Global o en algún espacio de nombres personalizado (más sobre eso en un segundo). Mientras pensaba en construir el AA, me surgió una pregunta: "Si uso los elementos de la GUI como claves en mi AA, ¿qué usaría para los valores en mis pares clave-valor?" Me di cuenta de que, debido a la forma en que funcionan los AA, podía dejarlos vacíos, es decir, usar " Vacío() "como mi valor, pero por alguna razón me pareció una pérdida de oportunidad. Mientras pensaba más en esto, me di cuenta de que, si pusiera los valores predeterminados para los elementos de la GUI en los pares clave-valor AA, podría configurar una función "Restablecer" (que restauraría la GUI a su valor predeterminado). estado) usando el mismo código! ¡Podría admitir dos funciones por el precio de una! ¿Y a quién no le gusta un buen dos-fer?
Sí, los espacios de nombres son útiles
Volviendo al tema de espacios de nombres por un segundo. Los espacios de nombres, para los no iniciados, pueden parecer algo místico y misterioso de lo que oyen hablar en Cumbre de descubrimiento del JMP u ocasionalmente en una publicación de blog. Déjame reventar esa burbuja ahora mismo: no son tan especiales. Sin embargo, son increíblemente útiles y los mejores scripters JSL han aprendido a utilizarlos de forma eficaz. (Para ser claros, no me cuento en ese grupo, pero almorzo con ellos ocasionalmente en SAS HQ o Discovery Summit... así que tal vez eso cuente algo).
Para darle una idea de qué es y qué hace un espacio de nombres, veamos los diccionarios. Si estás escribiendo algo en Inglés y necesitabas buscar una palabra, usarías un Inglés Diccionario. Si comenzaras a trabajar en Francés y necesitabas buscar una palabra, usarías un Francés diccionario. A veces, gracias a la conquista normanda de 1066, la misma palabra podía aparecer en ambos diccionarios pero podía tener significados ligeramente diferentes. Generalmente esto no sería un problema porque tienes el documento. contexto para ayudarte. Si estás trabajando en un Francés documento, alcanzas el Francés diccionario – no hay problema. Ni siquiera es un problema si tienes una combinación de francés e inglés en el mismo documento, siempre y cuando tengas algo de contexto pistas que le ayudarán a determinar qué diccionario utilizar.¿Qué tiene esto que ver con JSL? Los espacios de nombres son como diccionarios. Son lugares donde puede colocar variables y funciones personalizadas que le dan a JMP contexto adicional para comprender lo que quiere que haga. Por ejemplo, en STK, uso un nombre de función anotarVentana() en varios lugares. De hecho, uso una función con este nombre en cada parte del software donde dibujo algo en una ventana existente.En total, probablemente tengo media docena de funciones con el nombre anotarVentana() . El problema es que cada versión hace algo ligeramente diferente. Pero, como cada uno existe en su propio espacio de nombres, esto nunca es un problema. Incluso si tengo dos GUI diferentes abiertas al mismo tiempo. JMP sabe que cuando trabajo en un espacio de nombres espero anotarVentana() hacer una cosa y cuando estoy en otra espero que haga otra cosa. Esta estrategia simplificó significativamente el proceso de diseño de GUI en STK. Entonces, los espacios de nombres te permiten proporcionar contexto para que JMP sepa qué versión de una función desea usar en un script en particular. También pueden existir en la memoria después de que se haya cerrado el complemento que los creó. Y son estos dos puntos los que los hacen particularmente útiles para nuestra solución de Retiro.
Es hora de codificar
Bien, basta de teoría: pirateemos algo de código. Además, el resto de esto tendrá más sentido si lo explico sobre la marcha. En primer lugar, soy partidario del uso de bibliotecas en mis scripts; es la mejor práctica en la mayoría de situaciones de desarrollo. La mayoría de los lenguajes de programación pueden hacerlo; JSL también puede hacerlo. Entonces, mi solución Recall se construirá en forma de biblioteca. Para usar la biblioteca en una aplicación o script, el desarrollador solo necesita usar un incluir() argumento al inicio del script con una ruta de archivo a la biblioteca.
En segundo lugar, soy fanático de las funciones de conversación. Es realmente útil tener el guión indicándote lo que está haciendo en cada paso del camino. Pero también quiero poder decirle a mis funciones de conversación que ciérralo cuando termine de probar y codificar. (Las funciones conversadoras pueden ralentizar la ejecución de su código si no tiene cuidado). Si miras la biblioteca real en el Intercambio de archivos, verás mucha escritura () y espectáculo() argumentos. Estos están contenidos en un Si (detallado == 1, <…>) declaración que me permite apagarlos pasando un detallado = 0 comando a la función. Utilizo mucho esta construcción en mis scripts y normalmente solo pongo el verboso variable en la parte superior del script para que pueda activar y desactivar el registro detallado para todo el script ajustando un parámetro. Para mantener el código breve aquí, los eliminé de los fragmentos a continuación. La biblioteca completa se puede encontrar en el Recuperar biblioteca de funciones .
La primera función en la biblioteca se llama. matrices genéricas() . Esta función crea un AA a partir de una lista de elementos en la GUI y una lista de sus valores predeterminados. La función en realidad hace dos copias del AA: una para admitir las funciones de recuperación, llamada ::Configuración de usuario , y uno para soportar las funciones de Reset, llamado ::Valores predeterminados . Estas dos matrices se guardan en el espacio de nombres global. El " :: El operador ”al principio del nombre de una variable hace esto. La otra cosa a tener en cuenta es que estoy usando un Evaluar(Analizar()) estructura. Esta es una estrategia común para crear JSL dinámico que puede resultar engorroso si se utilizan esquemas de sustitución. Lo último que debo notar es que yo cerrar la variable de configuración del usuario. Esto es para evitar que el AA o cualquiera de los valores se sobrescriban accidentalmente.
genArrays = Function( {objects, values, platformName, verbose = 1},
{Default Local},
// Create the defaults Array
defaultString = "::" || platformName || "Defaults = Associative Array( objects, values, Empty() )";
defaultStringName = "::" || platformName || "Defaults";
// Run the default command
Eval( Parse( defaultString ) );
// Create the defaults Array
userString = "::" || platformName || "UserSettings = ::" || platformName || "Defaults";
userStringName = "::" || platformName || "UserSettings";
// Try to create the user settings Array and lock it so it's not overwritten.
Try(
Eval( Parse( userString ) );
Eval( Parse( "Lock Globals( " || platformName || "UserSettings )" ) );
,
Write(
"\!N\!NThe Associative Array \!"" || platformName || "UserSettings\!" is already present in memory and ready for use.\!N\!N"
)
);
);
Tomémonos un segundo y veamos lo que hay en las listas que se utilizan para construir el AA. Cuando estaba descubriendo cómo abordar este problema, tuve que lidiar con el problema de que hay varios tipos de << obtener() y << establecer() argumentos que necesitaba manejar. Lo que se me ocurrió fue agregar un encabezado a la parte clave del par clave-valor en AA. Estas son una o dos letras que se colocan delante de la clave (el elemento GUI). Para elementos GUI que usan << establecer() el encabezado es s^ . El << Establecer elementos() y << Establecer texto() usar si^ y st ^ respectivamente. Si tuviera una GUI (la llamaremos “ limpioGUI ”) con un cuadro de texto ( tb1 ), un cuadro numérico ( nb1 ) y un cuadro de lista ( lb1 ) la lista de objetos se vería así:
objects = {st^tb1, s^nb1, si^lb1};
Si dijéramos que queremos que estos tengan valores específicos cuando se ejecute el script o cuando el usuario presione un botón Restablecer, podríamos configurar el valores lista con lo que queríamos. voy a usar un Vacío() comando para que el cuadro de lista borre el contenido.
values = {“hello world”, 42, Empty()};
Ahora que tenemos nuestras dos listas, podemos usar el GenArrays() función para generar las matrices como esta:
GenArrays(objects, values, “neatGUI”);
Esto creará las dos AA en el espacio de nombres global. En el momento de la creación, ambos se verían así:
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
Ahora, todo lo que tenemos que hacer es proporcionar las siguientes tres funciones con el mismo nombre GUI ( limpioGUI ) ¡y él se encargará del resto! Veamos lo que hacen.
La primera de las funciones que realmente interactúa con la GUI es restablecerRoles() . Esta función es básicamente un bucle que extrae cada clave y valor en el AA predeterminado y utiliza la información para restablecer los elementos de la GUI a los valores predeterminados.Dado que los AA son listas “especiales”, necesitamos usar instrucciones diferentes para trabajar con ellas. Entonces, en lugar de simplemente obtener el primer valor en AA usando un subíndice, uso el << Primero mensaje. A partir de aquí, simplemente separa la parte del encabezado del elemento GUI en la clave.Estas piezas se pasan a cierta lógica, así que uso el derecho << establecer() mensaje con el valor del AA para la clave actual. Finalmente, todo esto se ejecuta usando otro Evaluar(Analizar()) argumento. Luego, todo el proceso se repite para cada par clave-valor en el AA.
resetRoles = Function( {platformName, verbose = 1},
{Default Local},
// Get the AA for the provided name
arrayName = Eval( Parse( "::" || platformName || "Defaults" ) );
arrayNameString = "::" || platformName || "UserSettings";
// Setup a logical break if an AA isn't provided
Try(
// Start the function
// Check if arrayName is an AA
If( Is Associative Array( arrayName ),
//Initialize the counter
currentKey = arrayName << First;
// Loop through the keys
For( i = 1, i <= N Items( arrayName ), i++,
// Extract the header and the key
stAA = currentkey; // The header-key pair
stType = Word( 1, stAA, "^" ); // The header part
stArg = Word( 2, stAA, "^" ); // The key part
// Match the operation header to the correct operation instruction and build the string
stArgString = Match( stType,
"si", stArg || " << Set Items( " || Char( arrayName[currentkey] ) || " )",
"s", stArg || " << Set( " || Char( arrayName[currentkey] ) || " )",
"st", stArg || " << Set Text( \!"" || Char( arrayName[currentkey] ) || "\!" )"
);
// Eval-Parse the string
Try( Eval( Parse( stArgString ) ) );
// Get the next key in the AA
nextkey = arrayName << Next( currentKey );
// set it as the target key for the next loop
currentkey = nextkey;
);
,
// Stop execution if not an AA.
Throw()
);
,
);
);
Las otras dos funciones, almacenarRoles() y recordarRoles() , funcionan de manera similar con algunas pequeñas diferencias. Primero, ambos operan en el usuario AA en lugar de por defecto AA creado por GenArrays () . Segundo, almacenarRoles() usos << obtener() mensajes y sobrescribe el valor parte del par clave-valor de un elemento de la GUI, por lo que se almacena para recuperarlo más adelante. También se incluye un código para desbloquear y volver a bloquear el AA del usuario durante la edición. Por último, los roles de recuperación () La función utiliza el << Agregar() mensaje con el si^ encabezado en lugar del << establecer() mensaje. En lugar de poner el código aquí, lo remitiré al biblioteca en el intercambio de archivos .
Entonces, después de pensar un poco, codificar, aplicar liberalmente la tecla Eliminar (retroceso para aquellos atrapados en el mundo de Windows... mi más sentido pésame) y un poco más de codificación, aprendí que es completamente posible crear un conjunto de funciones para admitir Recuperar/Reiniciar. utilizando una matriz asociativa. En retrospectiva, para una GUI simple de dos o tres elementos, todo este proceso es, sin duda, un poco Rube-Goldberg-esque . Pero imagina una GUI con 10 o 20 elementos. Este enfoque escala mucho mejor que el enfoque tradicional. Sólo tienes que realizar un seguimiento de una lista de lo que deseas recuperar/restablecer y sus valores iniciales. Y ni siquiera necesitas lucir un fuerte acento austriaco o luchar contra un gobernador hambriento de poder por el control de Marte para hacerlo.