While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.在更新半导体工具包时,我寻找了一种更好的方法来重建召回功能。
打开分销平台JMP . 看看右边的那一小组按钮——特别是“调用”按钮。 它无处不在。 JMP 中的几乎每个平台都有它。 它已融入 JMP 设计语言的 DNA。 现在想一想,这个小按钮多久在您与数据的日常战斗中拯救了您的腰包。 很多,对吧? 这可能是 JMP 中最被认为理所当然的功能之一。 但我认为这是使 JMP 如此有用的无数小改动之一。
现在,JSL 脚本编写者考虑一下在自定义加载项中复制该 Recall 函数。 这是一项相当高级的任务——我见过大多数人(包括我自己)这样做的方式将涉及在自定义或全局名称空间中为您要调用的每个 GUI 元素创建一个变量。 然后是在运行时从每个 GUI 元素收集信息的支持功能,以及在按下 Recall 按钮时将信息写回的支持功能。它。 是。 一个。 皇家。 疼痛。在 JMP 脚本和自定义应用程序中设置调用功能可能是目前最费力的任务之一。 对于 GUI 中的一两个组件,这没什么大不了的,但标准流程不能很好地扩展。 然而,对于这项工作,召回功能为插件提供了水平的润色和易用性,将其提升到了一个新的专业水平。 当加载项中有召回功能时,用户会喜欢它。 而且,坦率地说,我讨厌编码。
我们可以重建它
让我后退一点。 这篇文章讨论的想法始于 2016 年初。 我刚开始更新半导体工具包(STK) 到版本 2。 由于我将在另一篇博文中介绍的各种原因,我决定将整个项目迁移到应用程序生成器在JMP .这意味着对代码库和 GUI 进行大量修改,整理东西,尽可能标准化代码并添加同事贡献的一些新功能(再次感谢,伙计们!)。 不幸的是,这项工作破坏了版本 1 中所有现有的 Recall 代码。 面对在插件中为所有平台重建 Recall 代码的任务,我想,“必须有另一种方法来解决这个问题。 必须有一种方法来减少所有重复的编码。”
大约在同一时间,我正在研究如何将 JSL 中的关联数组 (AA) 用于 STK 中的一些其他功能。 我没有看到人们经常使用它们,但 AA 可以成为您脚本库中非常强大的工具。 为了便于讨论,AA 是一种特殊的列表。 您可以搜索它,放入东西,取出东西等。 它们的特别之处在于列表中的每个条目都有两部分,一个键和一个值。 在 AA 上有一些很棒的博客文章和资源( JMP 文档关于将关联数组列表转换为数据表JSL 提示:在列表中查找唯一值) 我在使用 AA 时参考的。 它们涵盖了许多关于 AA 的材料,并有一些很好的例子。 回到我最初的痛点,我想到全局变量名称及其内容可能会被翻译成 AA 中的键值对。 所以,作为我此刻的缪斯,我想知道是否可以使用关联数组来构建更好的召回。
好的,为了帮助理解我的想法,让我们看看通常是如何完成的。 实现 Recall 需要两个基本操作。 当用户执行一些触发动作,即点击“确定”时,所有需要调用的GUI元素,即列表框、单选框、文本框等,都需要以某种方式拉取并存储其内容。 然后,当用户点击“Recall”按钮时,需要将存储的内容推送回GUI中的适当位置。 这两个操作的核心是一对 JSL 消息: << 得到()和<< 设置() . 这些根据 GUI 元素有不同的风格(不要问我为什么–我没有写 JSL –我只是使用这些东西)但它们基本上都是这两者的变体并且做同样的事情。 JMP 中的脚本索引(Help > Scripting Index)是一种很好的方式来明确哪个与哪个 GUI 元素搭配。
所以,我的基于 AA 的解决方案必须做很多事情。 首先,我必须能够使用<< 得到()和<< 设置()在他们所有的排列中。 我必须能够告诉它我想要保存哪些 GUI 元素。 而且,我必须在某个地方保存这些信息,这些信息在加载项完成运行时不会消失。 这意味着修改后的工作流程将涉及使用我要恢复的 GUI 元素列表来构建 AA。 我需要将此 AA 放入 Global 或某个自定义命名空间(稍后会详细介绍)。 当我考虑构建 AA 时,我遇到了一个问题,“如果我在我的 AA 中使用 GUI 元素作为键,我会为我的键值对中的值使用什么?”我意识到,由于 AA 的工作方式,我可以将它们留空,即使用“空的() “作为我的价值,但出于某种原因,这感觉像是在浪费机会。 当我更多地思考这个问题时,我意识到,如果我将 GUI 元素的默认值放入 AA 键值对中,我实际上可以设置一个“重置”功能(这会将 GUI 恢复为默认值)州)使用相同的代码! 我可以以一个的价格支持两个功能! 而且,谁不喜欢一个好的二费?
是的,命名空间很有用
回到问题命名空间一秒钟。 对于外行来说,命名空间可能看起来像是他们在JMP 发现峰会或偶尔在博客文章中。 让我现在就戳破那个泡泡——它们没那么特别。 然而,它们非常有用,最好的 JSL 脚本编写者已经学会有效地使用它们。 (需要明确的是,我不把自己算在那个群体中,但我偶尔会在 SAS 总部或探索峰会上与他们共进午餐……所以也许这很重要。)
为了让您了解命名空间是什么以及做什么,让我们看一下字典。 如果你正在写一些东西英语并且需要查找一个词,你会使用英语字典。 如果你开始工作法语并且需要查找一个词,你会使用法语字典。 有时,由于 1066 年的诺曼征服,同一个词可能会出现在两个词典中,但含义可能略有不同。 这通常不会成为问题,因为您有文档语境来帮助你。 如果你正在做一个法语文件,你伸手去拿法语字典——没问题。 如果您在同一个文档中混合使用法语和英语,这甚至不是问题,只要您有一些语境帮助您找出要查找的字典的线索。这与 JSL 有什么关系? 命名空间就像字典。 它们是您可以放置自定义变量和函数的地方,这些变量和函数为 JMP 提供额外的上下文以了解您想要它做什么。 例如,在 STK 中,我使用函数名注释窗口()在几个地方。 事实上,我在软件的每个部分中都使用了一个具有此名称的函数,我在现有窗口上绘制了一些东西。总的来说,我可能有六个函数的名字注释窗口() . 问题是每个版本的功能都略有不同。 但是,因为每个都存在于自己的命名空间中,所以这从来都不是问题。 即使我同时打开了两个不同的 GUI。 JMP 知道,当我在一个命名空间中工作时,我期望注释窗口()做一件事,当我在另一件事时,我希望它做别的事情。 这种策略显着简化了 STK 中的 GUI 设计过程。 所以,命名空间让你提供语境让 JMP 知道您希望它在特定脚本中使用哪个版本的函数。 在创建它们的加载项关闭后,它们也可以存在于内存中。 正是这两点使它们对我们的召回解决方案特别有用。
是时候编码了
好的,足够的理论 - 让我们破解一些代码。 此外,如果我边走边解释,其余部分会更有意义。 首先,我提倡在我的脚本中使用库,这是大多数开发情况下的最佳实践。 大多数编程语言都可以做到——JSL 也可以。 因此,我的 Recall 解决方案将以库的形式构建。 要在应用程序或脚本中使用该库,开发人员只需要使用包括()脚本开头的参数与库的文件路径。
其次,我喜欢聊天功能。 让脚本告诉你它在每一步都在做什么真的很方便。 但是,我也希望能够告诉我的聊天功能关掉它当我完成测试和编码时。 (如果您不小心,Chatty 函数会减慢您的代码执行速度)。 如果你看实际的图书馆在文件交换中,你会看到很多写()和节目()论据。 这些包含在一个如果(详细 == 1,<…>)让我通过传递一个来关闭它们的声明详细 = 0命令到函数。 我在我的脚本中经常使用这个结构,我通常只是把冗长的脚本顶部的变量,以便我可以通过调整一个参数来打开和关闭整个脚本的详细日志记录。 为了保持这里的代码简短,我已经从下面的代码片段中删除了这些。 完整的库可以在调用函数库.
调用库中的第一个函数genArrays() . 此函数根据 GUI 中的元素列表和它们的默认值列表生成 AA。 该函数实际上制作了 AA 的两份副本——一份用于支持 Recall 函数,称为::<平台名称>用户设置, 和一个支持复位功能, 称为::<平台名称>默认值. 这两个数组被保存到全局命名空间中。 这 ” :: ” 变量名前面的运算符执行此操作。 另一件需要注意的是,我正在使用评估(解析())结构体。 这是构建动态 JSL 的常用策略,使用替换方案可能会很麻烦。 最后要注意的是,我锁用户设置变量。 这是为了防止 AA 或任何值被意外覆盖。
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"
)
);
);
让我们花点时间看看用于构造 AA 的列表中有哪些内容。 当我在弄清楚如何解决这个问题时,我不得不处理这样一个问题: << 得到()和<< 设置()我需要处理的论点。 我想出的是在 AA 中的 Key-Value 对的 Key 部分添加一个标题。 这些是放在键(GUI 元素)前面的一两个字母。 对于使用的 GUI 元素<< 设置()标题是^ . 这<< 设置项目()和<< 设置文本()利用西^和圣^分别。 如果我有一个 GUI(我们称之为“整洁的GUI ”)带有文本框( tb1 ), 一个数字框 ( NB1 ) 和一个列表框 (磅1 ) 对象列表如下所示:
objects = {st^tb1, s^nb1, si^lb1};
如果我们说我们希望这些在脚本运行或用户按下重置按钮时具有特定值,我们可以设置价值观列出我们想要的。 我将使用一个空的()命令用于清除列表框的内容。
values = {“hello world”, 42, Empty()};
现在我们已经有了两个列表,我们可以使用GenArrays()生成这样的数组的函数:
GenArrays(objects, values, “neatGUI”);
这将在 Global 命名空间中创建两个 AA。 在创建时,它们都看起来像这样:
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
现在,我们所要做的就是为接下来的三个函数提供相同的 GUI 名称(整洁的GUI ),它会照顾其余的! 让我们看看他们做了什么。
实际与 GUI 交互的第一个函数是重置角色() . 这个函数基本上是一个循环,它拉取默认 AA 中的每个键和值,并使用这些信息将 GUI 元素重置为默认值。由于 AA 是“特殊”列表,我们需要使用不同的指令来处理它们。 因此,我不只是使用下标获取 AA 中的第一个值,而是使用<< 第一个信息。 从这里开始,它只是将标题部分与键中的 GUI 元素分开。这些部分被传递给一些逻辑,所以我使用正确的<< 设置()带有当前键的 AA 值的消息。 最后,这一切都使用另一个执行评估(解析())争论。 然后对 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()
);
,
);
);
另外两个函数,商店角色()和召回角色() , 以类似的方式工作,但有一些小的差异。 首先,它们都在用户AA 代替默认由 GenArrays 创建的 AA () . 第二,商店角色()用途<< 得到()消息并覆盖价值GUI 元素的键值对的一部分,以便存储以供以后调用。 还有一些代码用于在编辑期间解锁和重新锁定用户 AA。 最后,recallRoles ()函数使用<< 追加()与消息西^标题而不是<< 设置()信息。 我不会将代码放在这里,而是将您推荐给文件交换中的库.
因此,经过一些思考、编码、删除键的自由应用(对于那些困在 Windows 世界中的人来说,退格……我的哀悼),以及更多的编码,我了解到完全有可能构建一组函数来支持 Recall/Reset使用关联数组。 事后看来,对于一个简单的两元素或三元素 GUI,这整个过程诚然有点鲁布-戈德堡式. 但是,想象一个有 10 或 20 个元素的 GUI。 这种方法的扩展性比传统方法好得多。 您只需跟踪要调用/重置的内容及其初始值的列表。 而且,你甚至不需要带着浓重的奥地利口音,或者为了控制火星而与渴望权力的州长作战。