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.Durante l'aggiornamento del Semiconductor Toolkit, ho cercato un modo migliore per ricostruire la funzione Recall.
Apri la piattaforma di distribuzione in JMP . Guarda quel piccolo gruppo di pulsanti sulla destra, in particolare il pulsante "Richiama". È ovunque. Quasi tutte le piattaforme JMP ne sono dotate. È parte integrante del DNA del linguaggio di progettazione JMP. Ora pensa per un secondo a quanto spesso quel piccolo pulsante ti ha salvato la vita nel vivo della tua battaglia quotidiana con i dati. Molto, vero? Probabilmente è una delle funzionalità di JMP più data per scontata. Ma direi che è uno della miriade di piccoli accorgimenti che rendono JMP tanto utile quanto lo è.
Ora, gli scripter JSL, pensano per un secondo a replicare la funzione Recall in un componente aggiuntivo personalizzato. È un compito piuttosto avanzato: il modo in cui ho visto farlo la maggior parte delle persone (me compreso) comporterebbe la creazione di una variabile in uno spazio dei nomi personalizzato o globale per ogni elemento della GUI che desideri richiamare. Poi ci sono le funzioni di supporto per raccogliere le informazioni da ciascun elemento della GUI in fase di runtime e quelle per riscrivere le informazioni quando viene premuto il pulsante Richiama.Esso. È. UN. Reale. Dolore.Configurare una funzionalità di richiamo negli script JMP e nelle applicazioni personalizzate è probabilmente uno dei compiti più laboriosi in circolazione. Per uno o due componenti in una GUI, non è un grosso problema, ma il processo standard non è ben scalabile. Eppure, a fronte di tale sforzo, la funzionalità di richiamo conferisce un livello di rifinitura e facilità d'uso a un componente aggiuntivo che lo eleva a un nuovo livello di professionalità. Gli utenti adorano quando è presente una funzionalità di richiamo in un componente aggiuntivo. E, ad essere sincero, odio codificarlo.
Possiamo ricostruirlo
Permettimi di tornare un po' indietro. L’idea di cui si parla in questo post è nata all’inizio del 2016. Avevo appena iniziato ad aggiornare il Kit di strumenti per semiconduttori (STK) alla versione 2. Per vari motivi che tratterò in un altro post del blog, ho deciso di migrare l'intero progetto su Generatore di applicazioni In JMP .Ciò ha significato armeggiare molto con il codice base e le GUI, mettere in ordine le cose, standardizzare il codice ove possibile e aggiungere alcune nuove funzionalità fornite dai colleghi (grazie ancora, ragazzi!). Sfortunatamente, questo lavoro ha rotto tutto il codice esistente per Recall dalla versione 1. Di fronte al compito di ricostruire il codice Recall per tutte le piattaforme nel componente aggiuntivo, ho pensato: “Deve esserci un altro modo per affrontare questo problema. Deve esserci un modo per ridurre tutta quella codifica ripetitiva.
Più o meno nello stesso periodo, stavo studiando come utilizzare gli array associativi (AA) in JSL per alcune altre funzionalità in STK. Non vedo persone che li utilizzino molto, ma gli AA possono essere uno strumento incredibilmente potente nel tuo arsenale di scripting. Ai fini di questa discussione, un AA è un tipo speciale di elenco. Puoi cercarlo, inserire cose, togliere cose, ecc. Ciò che li rende speciali è che ogni voce nell'elenco ha due parti, una chiave e un valore. Ci sono alcuni ottimi post e risorse di blog sugli AA ( Documentazione JMP Sulla conversione di un elenco di array associativi in una tabella di dati Suggerimento JSL: trovare valori univoci in un elenco ) a cui faccio riferimento quando lavoro con gli AA. Coprono gran parte del materiale sugli AA e contengono alcuni ottimi esempi. Tornando al punto dolente iniziale, mi è venuto in mente che il nome della variabile globale e il suo contenuto potrebbero probabilmente essere tradotti in una coppia chiave-valore in un AA. Quindi, essendo la mia musa ispiratrice del momento, mi chiedevo se potevo usare un Associative Array per costruire un Recall migliore.
OK, per dare un senso a ciò che stavo pensando, vediamo come viene fatto normalmente. Sono necessarie due operazioni di base per implementare Recall. Quando l'utente esegue un'azione di attivazione, ad esempio fa clic su "OK", tutti gli elementi della GUI, ad esempio caselle di elenco, caselle radio, caselle di testo, ecc., per essere richiamati devono essere estratti e archiviati in qualche modo. Quindi, quando l'utente fa clic sul pulsante "Richiama", i contenuti archiviati devono essere reinseriti nelle posizioni appropriate nella GUI. Al centro di queste due operazioni ci sono una coppia di messaggi JSL: << prendi() E < . Ne esistono diversi tipi a seconda dell'elemento GUI (non chiedermi perché – Non ho scritto JSL – Io uso solo quelle cose) ma fondamentalmente sono solo varianti di questi due e fanno le stesse cose. L'indice di scripting in JMP (Guida > Indice di scripting) è un ottimo modo per sapere quale va con quale elemento della GUI.
Quindi, la mia soluzione basata su AA deve fare una serie di cose. Innanzitutto devo essere in grado di utilizzare << prendi() E < in tutte le loro permutazioni. Devo essere in grado di dirgli quali elementi della GUI voglio salvare. Inoltre, devo avere un posto dove salvare queste informazioni che non scompaiano una volta terminata l'esecuzione del componente aggiuntivo. Ciò significa che il flusso di lavoro rivisto comporterebbe la creazione di un AA utilizzando un elenco degli elementi della GUI che desidero ripristinare. Avrei bisogno di inserire questo AA nello spazio dei nomi globale o in uno spazio dei nomi personalizzato (ne parleremo più avanti tra un secondo). Mentre pensavo di costruire l'AA sono arrivato a una domanda: "Se utilizzo gli elementi della GUI come chiavi nel mio AA, cosa userei per i valori nelle mie coppie chiave-valore?" Mi sono reso conto che, visto il modo in cui funzionano gli AA, potevo semplicemente lasciarli vuoti, ovvero utilizzare " Vuoto() " come il mio valore, ma per qualche motivo mi è sembrata un'opportunità davvero sprecata. Mentre ci pensavo ancora, mi sono reso conto che, se avessi inserito i valori predefiniti per gli elementi della GUI nelle coppie chiave-valore AA, avrei potuto effettivamente impostare una funzione "Reset" (che ripristinerebbe la GUI al suo valore predefinito stato) utilizzando lo stesso codice! Potrei supportare due funzionalità al prezzo di una! E chi non ama un buon due-fer?
Sì, gli spazi dei nomi sono utili
Torniamo alla questione di spazi dei nomi per un secondo. Gli spazi dei nomi, per chi non lo sapesse, possono sembrare una cosa mistica e misteriosa di cui sentono parlare Summit sulla scoperta di JMP o occasionalmente in un post del blog. Fammi scoppiare quella bolla adesso: non sono così speciali. Sono, tuttavia, incredibilmente utili e i migliori scripter JSL hanno imparato a usarli in modo efficace. (Per essere chiari, non mi considero in quel gruppo, ma ogni tanto pranzo con loro al quartier generale della SAS o al Discovery Summit... quindi forse conta qualcosa.)
Per darti un'idea di cosa è e cosa fa uno spazio dei nomi, diamo un'occhiata ai dizionari. Se stai scrivendo qualcosa in Inglese e avessi bisogno di cercare una parola, dovresti usare an Inglese Dizionario. Se dovessi iniziare a lavorare in francese e dovevi cercare una parola, dovresti usare a francese dizionario. A volte, grazie alla conquista normanna del 1066, la stessa parola poteva comparire in entrambi i dizionari ma avere significati leggermente diversi. Questo generalmente non sarebbe un problema perché hai il documento contesto per aiutarti. Se stai lavorando su a francese documento, prendi il file francese dizionario – nessun problema. Non è nemmeno un problema se nello stesso documento è presente un mix di francese e inglese, purché ne sia presente qualcuno contesto indizi per aiutarti a capire quale dizionario cercare.Cosa c'entra questo con JSL? Gli spazi dei nomi sono come i dizionari. Sono luoghi in cui puoi inserire variabili e funzioni personalizzate che forniscono a JMP un contesto aggiuntivo per capire cosa vuoi che faccia. Ad esempio, nell'STK, utilizzo un nome di funzione annotateWindow() in diversi posti. Infatti utilizzo una funzione con questo nome in ogni parte del software in cui disegno qualcosa su una finestra esistente.In totale, probabilmente ho una mezza dozzina di funzioni con questo nome annotateWindow() . Il problema è che ogni versione fa qualcosa di leggermente diverso. Ma poiché ognuno esiste nel proprio spazio dei nomi, questo non è mai un problema. Anche se ho due GUI diverse aperte contemporaneamente. JMP sa che quando lavoro in uno spazio dei nomi mi aspetto annotateWindow() fare una cosa e quando sono in un'altra mi aspetto che faccia qualcos'altro. Questa strategia ha notevolmente semplificato il processo di progettazione della GUI in STK. Pertanto, gli spazi dei nomi ti consentono di fornire contesto affinché JMP sappia quale versione di una funzione si desidera venga utilizzata in un particolare script. Possono anche esistere in memoria dopo la chiusura del componente aggiuntivo che li ha creati. E sono questi due punti che li rendono particolarmente utili alla nostra soluzione Recall.
È tempo di codificare
OK, basta teoria: creiamo un po' di codice. Inoltre, il resto avrà più senso se lo spiego man mano che procedo. Innanzitutto, sono un sostenitore dell'utilizzo delle librerie nei miei script, è la migliore pratica nella maggior parte delle situazioni di sviluppo. La maggior parte dei linguaggi di programmazione può farlo, anche JSL può farlo. Quindi, la mia soluzione Recall sarà realizzata sotto forma di libreria. Per utilizzare la libreria in un'applicazione o in uno script, lo sviluppatore deve semplicemente utilizzare un file includere() argomento all'inizio dello script con un percorso file alla libreria.
In secondo luogo, sono un fan delle funzioni chat. È davvero utile avere lo script che ti dice cosa sta facendo in ogni fase del processo. Ma voglio anche poter raccontare le mie funzioni chiacchierone chiuderlo quando avrò finito di testare e scrivere codice. (Le funzioni chatty possono rallentare l'esecuzione del codice se non stai attento). Se guardi la biblioteca vera e propria nello Scambio file vedrai molta scrittura () E spettacolo() argomenti. Questi sono contenuti in un Se(verboso == 1, <…>) istruzione che mi consente di disattivarli passando a verboso = 0 comando alla funzione. Uso molto questo costrutto nei miei script e di solito inserisco semplicemente il file prolisso variabile nella parte superiore dello script in modo da poter attivare e disattivare la registrazione dettagliata per l'intero script regolando un parametro. Per mantenere il codice breve qui, li ho rimossi dagli snippet seguenti. La libreria completa può essere trovata in Richiama la libreria delle funzioni .
Viene chiamata la prima funzione nella libreria genArray() . Questa funzione crea un AA da un elenco di elementi nella GUI e un elenco dei loro valori predefiniti. La funzione in realtà crea due copie dell'AA, una per supportare le funzioni Recall, chiamata ::Impostazioni utente , e uno per supportare le funzioni di Reset, chiamato ::Predefiniti . Questi due array vengono salvati nello spazio dei nomi globale. IL " :: L'operatore "all'inizio del nome di una variabile fa questo. L'altra cosa da notare è che sto usando un file Valutazione(Analizza()) struttura. Questa è una strategia comune per la creazione di JSL dinamico che potrebbe risultare complicato utilizzando schemi di sostituzione. L'ultima cosa da notare è che io serratura la variabile delle impostazioni utente. Questo per evitare che l'AA o uno qualsiasi dei valori vengano sovrascritti 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"
)
);
);
Prendiamoci un secondo e guardiamo cosa c'è negli elenchi utilizzati per costruire l'AA. Quando stavo cercando di capire come affrontare questo problema, ho dovuto affrontare il problema che esistono diverse versioni del file << prendi() E < argomenti che dovevo gestire. Ciò che mi è venuto in mente è stato aggiungere un'intestazione alla parte Chiave della coppia Chiave-Valore nell'AA. Queste sono una o due lettere che vengono messe davanti alla chiave (l'elemento della GUI). Per gli elementi della GUI che utilizzano < l'intestazione è s^ . IL << Imposta elementi() E << Imposta testo() utilizzo si^ E st^ rispettivamente. Se avessi una GUI (la chiameremo “ GUI pulita ") con una casella di testo ( tb1 ), una casella numerica ( nb1 ) e una casella di riepilogo ( lb1 ) l'elenco degli oggetti sarebbe simile a questo:
objects = {st^tb1, s^nb1, si^lb1};
Se dicessimo che vogliamo che questi abbiano valori specifici quando lo script viene eseguito o quando l'utente preme un pulsante Reimposta, potremmo impostare il valori elenco con quello che volevamo. Utilizzerò un Vuoto() comando per la casella di riepilogo per cancellare il contenuto.
values = {“hello world”, 42, Empty()};
Ora che abbiamo i nostri due elenchi, possiamo usare il file GenArray() funzione per generare gli array in questo modo:
GenArrays(objects, values, “neatGUI”);
Ciò creerà i due AA nello spazio dei nomi globale. Alla creazione, sarebbero entrambi simili a questo:
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
Ora, tutto ciò che dobbiamo fare è fornire alle tre funzioni successive lo stesso nome GUI ( GUI pulita ) e al resto ci penserà lui! Diamo un'occhiata a cosa fanno.
La prima delle funzioni che effettivamente interagisce con la GUI è resetRuoli() . Questa funzione è fondamentalmente un ciclo che estrae ogni chiave e valore nell'AA predefinito e utilizza le informazioni per reimpostare gli elementi della GUI sui valori predefiniti.Poiché gli AA sono elenchi “speciali”, è necessario utilizzare istruzioni diverse per lavorare con essi. Quindi, invece di ottenere semplicemente il primo valore nell'AA utilizzando un pedice, utilizzo the < Messaggio. Da qui, è sufficiente separare la parte dell'intestazione dall'elemento GUI nella chiave.Questi pezzi vengono passati a una logica, quindi utilizzo il diritto < messaggio con il valore dell'AA per la chiave corrente. Infine, tutto questo viene eseguito utilizzando un altro Valutazione(Analizza()) discussione. L'intero processo si ripete quindi per ciascuna coppia chiave-valore nell'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()
);
,
);
);
Le altre due funzioni, storeRoles() E richiamoRuoli() , funzionano in modo simile con alcune piccole differenze. Innanzitutto, entrambi operano su utente AA invece di predefinito AA creato da GenArrays () . Secondo, storeRoles() usi << prendi() messaggi e sovrascrive il file valore parte della coppia chiave-valore per un elemento GUI in modo che venga archiviato per un successivo richiamo. È incluso anche del codice per sbloccare e ribloccare l'AA dell'utente durante la modifica. Infine, il richiamoRoles () la funzione utilizza il file < messaggio con il si^ intestazione invece di < Messaggio. Invece di inserire il codice qui, ti rimanderò al file libreria nello scambio file .
Quindi, dopo un po' di riflessione, codifica, applicazione libera del tasto Elimina (backspace per chi è bloccato nel mondo Windows... le mie condoglianze) e un po' più di codifica, ho appreso che è del tutto possibile creare una serie di funzioni per supportare Recall/Reset utilizzando un array associativo. Col senno di poi, per una semplice GUI a due o tre elementi, l'intero processo è, bisogna ammetterlo, un po' Alla Rube-Goldberg . Ma immagina una GUI con 10 o 20 elementi. Questo approccio è molto più scalabile rispetto all’approccio tradizionale. Devi solo tenere traccia di un elenco di ciò che vuoi richiamare/resettare e dei loro valori iniziali. E non è nemmeno necessario sfoggiare un forte accento austriaco o combattere un governatore assetato di potere per il controllo di Marte per farlo.