脚本编写者在尝试将变量用作 JSL 函数或消息中的参数但未获得预期结果时,经常会感到困惑。虽然在 JSL 中,脚本编写者遇到这种情况的情况有很多,但用户经常遇到的情况是尝试设置“规格限制”列属性时。用作“规格限制”的值通常是从上一步中的查找表中提取的,并分配给变量。收集所有限制值并将其分配给变量后,下一个合乎逻辑的步骤是使用这些变量设置分析表中的列属性。以下是脚本编写者可能尝试的代表性示例。您可以将此 JSL 复制到脚本窗口中并运行它以继续操作。
Names Default to Here(1);
// Open a sample data table
dt = Open("$SAMPLE_DATA/Cleansing.jmp");
// Values previously obtained from a lookup table and assigned to variables as:
myLSL = 6;
myUSL = 8;
myTarget = 7;
// Use the variables to set the spec limits column property
Column(dt, "pH") << Set Property(
"Spec Limits",
{LSL( myLSL ), USL( myUSL ), Target( myTarget ), Show Limits( 1 )}
);
执行此脚本时,pH 列旁边会出现图标,表示已设置列属性,单击图标可确认它是规格限值属性。

但是,当您打开“列信息”对话框窗口查看规格限值时,却发现它们不见了!

JMP 做了什么?它似乎起作用了,但只是部分起作用!为什么?!
为了进行调查,我们可以使用 JSL 查看属性设置。也许这样做会提供一些线索。评估以下表达式,
Column(dt, "pH") << Get Property("Spec Limits");
导致将以下信息写入日志:
{LSL( myLSL ), USL( myUSL ), Target( myTarget ), Show Limits( 1 )}
好吧,这就是我所指定的,但为什么我看不到分配给变量的值,而看不到变量本身呢?这个问题的答案直指困惑的症结: JSL 并不总是对参数进行足够多次的求值以解析指定的值,并且求值的次数因变量的使用情况而异。例如,在当前上下文中,JMP“解析”列表参数以了解有项目可以指定 Spec Limits 属性的不同方面。这是一次求值。在另一次求值中,JMP 检查列表的项目以确定在 Spec Limits 属性中设置哪些限制(LSL、USL、Target 等)。JMP 根本不求值列表项目中提供的参数(myLSL、myUSL、myTarget)。JMP 期望将数值作为 LSL、USL 和 Target 的参数。当提供变量时,JMP 不会识别它,也不会尝试解析变量以确定关联的值。
那么,我能否知道在给定情况下一个参数将求值多少次?如果我需要更多求值,如上例所示,我该怎么办?由于我们无法访问 JMP 的源代码,因此很难知道在给定情况下将执行多少次求值。但是,这不如知道它是否足以满足当前用途重要。我做的第一件事就是尝试验证!
在事先不知道“规格限值”消息需要什么值的情况下,我编写的脚本与上面最初显示的值完全一样。接下来,我通过在“列信息”对话框中查看规格限值或使用列创建图表来检查是否设置了规格限值,以查看图表参考线是否按应有的方式显示。如果验证失败(如本例所示),我需要编辑表达式以删除变量并改为硬编码数值。
Column( dt, "pH" ) << Set Property(
"Spec Limits",
{LSL( 6 ), USL( 8 ), Target( 7 ), Show Limits( 1 )}
)
然后,我运行表达式来验证问题出在我使用的变量上,而不是其他语法问题。确认表达式提供了硬编码值的预期结果后,我知道我遇到了一种情况,即我的变量没有得到足够的评估,无法获得分配的值。因此,我需要让 JMP 在运行时动态创建表达式,以便在评估表达式之前将变量值嵌入其中。
现在有多种方法可以预先评估变量,但我首选的方法是使用 Eval Expr() 函数。其他方法包括使用 Substitute() 函数或通过连接字符串构建 JSL 表达式,然后解析和评估最终字符串。到目前为止,我最不喜欢的方法是将表达式构建为字符串。由于代码是单色的,因此很难阅读,因此我失去了 JSL 编辑器提供的有用的语义格式,并且嵌入的引号需要转义。更糟糕的是,代码很难调试和排除故障,因为我失去了评估选定代码片段的能力。
// An example of concatenating strings to dynamically write a JSL expression.
Parse(
"Column( dt, \!"pH\!" ) << Set Property(
\!"Spec Limits\!",
{LSL( " || Char(myLSL) || " ), USL( " || Char(myUSL) || " ), Target( " || Char(myTarget) || " ), Show Limits( 1 )}
)"
)
在这方面,使用 Substitute() 函数更好,但如果表达式很长,我发现很难读取表达式,同时还要查看指定在何处进行替换的后面的参数。
// An example of using Substitute() to dynamically write a JSL expression.
Substitute(
Expr(Column(dt, "pH") << Set Property(
"Spec Limits",
{LSL( lll ), USL( uuu ), Target( ttt), Show Limits( 1 )}
))
,
Expr(lll), myLSL,
Expr(uuu), myUSL,
Expr(ttt), myTarget
)
相比之下,Eval Expr() 函数允许内联替换规范,并且由于该函数的参数是一个表达式,因此 JSL 编辑器能够以语义格式显示它。
那么 Eval Expr() 如何工作?它是一个以表达式作为唯一参数并返回表达式的函数。它在提供的表达式中搜索标记 Expr()。然后,JMP 评估 Expr() 括号内的内容,并用返回值替换整个标记。回到上面设置规范限制的示例,如果我用 Expr()“包装”我的变量并将整个表达式放在 Eval Expr() 调用中,JMP 将在运行时用变量值替换我的变量(和 Expr() 标记)并返回修改后的表达式。所以,
// An example of using Eval Expr() to dynamically write a JSL expression.
Eval Expr(
Column(dt, "pH") << Set Property(
"Spec Limits",
{LSL( Expr(myLSL) ), USL( Expr(myUSL) ), Target( Expr(myTarget) ), Show Limits( 1 )} );
);
返回:
Column( dt, "pH" ) << Set Property(
"Spec Limits",
{LSL( 6 ), USL( 8 ), Target( 7 ), Show Limits( 1 )}
)
太棒了!我现在有一个表达式来设置包含所需硬编码值的 Spec Limits 属性。我刚刚动态编写了 JSL 代码!现在我只需要评估这个修改后的表达式。为此,Eval() 函数很有用。我可以包装 Eval Expr(),调用 Eval() 调用,然后 Eval Expr() 返回的表达式将立即被评估。这是一个完整的示例脚本,它将使用仅在运行时才知道的限制值正确设置 Spec Limits 属性:
Names Default to Here(1);
// Open a sample data table
dt = Open("$SAMPLE_DATA/Cleansing.jmp");
// Values previously obtained from a lookup table and assigned to variables as:
myLSL = 6;
myUSL = 8;
myTarget = 7;
// Use the variables to set the spec limits column property
Eval(Eval Expr(
Column(dt, "pH") << Set Property(
"Spec Limits",
{LSL( Expr(myLSL) ), USL( Expr(myUSL) ), Target( Expr(myTarget) ), Show Limits( 1 )}
);
));
现在,当我验证此脚本时,我会在“列信息”对话框中看到规格限制的值。

此外,评估这个表达式
Column(dt, "pH") << Get Property("Spec Limits");
导致将以下信息写入日志:
{LSL( 6 ), USL( 8 ), Target( 7 ), Show Limits( 1 )}
最后,如果我使用该列创建一个图表,我会在预期的位置看到规格限制参考线。

动态编写 JSL 代码是脚本编写者需要掌握的一项有用技能。在许多情况下,JMP 不会对表达式中的参数进行足够多次的求值,从而无法获得所需的实际值。虽然有多种动态编写 JSL 代码的技术,但我首选的方法是 Eval(EvalExpr(Expr())) 方法。它允许进行内联替换和语义格式化,这使得读取和调试代码相对容易。