Lessons from smart JSL mistakes

by on Jan.11, 2011 , under JMP & JSL

This is a follow-up to my pre­vi­ous post, “A script to check and change a JMP pref­er­ence ($2).

Back­ground

Mar­i­anne Toft asked the JMP script­ing group on LinkedInques­tion about man­ag­ing pref­er­ences with JSL:

Does any­body know how to read a spe­cific set­ting from the Pref­er­ences? I need to read the sta­tus of ‘Use JMP locale set­tings’ and if 0 then change to 1 and give the user a mes­sage to restart JMP.

A $2 solution

I wrote a $2 script that solves the prob­lem, and I sent Mar­i­anne a coupon code for a free copy, in thanks for post­ing such a thought-provoking JSL challenge.

My strat­egy is described in detail in that pre­vi­ous post, if you’d like to try writ­ing it your­self with a bunch of pointers.

It’s also avail­able as a $2 down­load if you’d rather just get the final code, fully com­mented and tested on JMP 8 and 9 on both Win­dows and Mac. One rea­son to con­sider buy­ing this script instead of a tall drip is that the script is abstracted to check and change any pref­er­ence, not just Use JMP Locale Set­tings( 0|1 ). That abstrac­tion reveals some script­ing tricks that might be help­ful in your other script­ing projects.

I’ll be delighted if some­one else comes up with a more ele­gant solu­tion, because mine is brute force. That’s because JMP doesn’t return the cur­rent pref­er­ences as an expres­sion or a string or any­thing else you could poten­tially oper­ate on with JSL. It just echoes the pref­er­ences to the Log win­dow, so you need to read the text of the Log win­dow and deal with that. If I’m miss­ing some­thing here, some­one please enlighten me!

Sev­eral smart mis­takes and lessons to learn from them

Sev­eral other peo­ple wrote help­ful sug­ges­tions. Unfor­tu­nately, their code didn’t work, and we can all learn some impor­tant things about JSL by study­ing their smart mistakes.

Mark Anawi sug­gested using Show Preferences(Use JMP Locale Settings) ) to see the set­ting for just that pref­er­ence. It’s a good guess at an argu­ment for the Show Preferences() com­mand, but it’s not actu­ally a sup­ported argu­ment. It doesn’t work. You’ll just get the usual out­put for Show Pref­er­ences() with­out an argument.

Les­son: when you sup­ply an argu­ment JMP doesn’t under­stand, some­times you get errors, but a lot of the time it’s just ignored, silently. You get no indi­ca­tion that you did some­thing that doesn’t work. The only way you can be sure it was ignored is to try it again with­out the argu­ment, or with a dif­fer­ent argu­ment, and check whether any­thing changed.

Peter Wiebe wrote an ele­gant solu­tion that uses a For() loop to step through each Arg() of an expres­sion, look­ing for and if nec­es­sary chang­ing the pref­er­ence in ques­tion. His code is con­cise and beautiful:

jmp_rulz = getpreferences();
For( i = 1, i <= N Arg( jmp_rulz ), i++,
    If( Contains( Char( Arg( jmp_rulz, i ) ),
        "Use JMP Locale Settings(0)" ),
        Preferences( Use JMP Locale Settings( 1 ) );
        New Window( "Restart JMP",
            Panel Box( "Restart JMP",
                Button Box( "OK", << closewindow )
            )
        )
    )
);

His code is far more ele­gant than mine. It’s an effi­cient strat­egy! Unfor­tu­nately, it doesn’t work, for sev­eral reasons.

Get Preferences() isn’t a com­mand in JMP 8 or 9, so the first line returns an error:

Name Unresolved: get preferences in access or evaluation of 'get preferences' , get preferences()

In the following script, error marked by /*###*/
::jmp_rulz = get preferences() /*###*/

I assume that was just a typo, though. Peter meant Show Preferences(), but as I warned at the out­set, that com­mand doesn’t return an expres­sion. It just echoes text to the Log win­dow. So, after run­ning the first line (with “get” changed to “show”), jmp_rulz con­tains . (missing).

There­fore, N Arg(jmp_rulz)i is never < 0, so the For() loop never runs.

I like Peter’s script bet­ter than mine. It should have worked—Show Preferences() really ought to return an expres­sion so that his For() loop can step through it and look for the spe­cific pref­er­ence in ques­tion. Unfor­tu­nately it doesn’t. I hope our heroes in JMP R&D will con­sider improv­ing this in a future version.

Why did Peter think this worked? Prob­a­bly because when he ran it, he didn’t get any error mes­sages. I didn’t either, and at first I was try­ing to fig­ure out why his script worked when I’d already proven to myself that Show Preferences() doesn’t return any­thing into the jmp_rulz global.

He prob­a­bly also checked the Log win­dow after run­ning it to con­firm that the pref­er­ence was on, saw that it was, and fig­ured that it must have worked. He wouldn’t have expected to see his “Restart JMP” win­dow unless it the set­ting was off to begin with, so he prob­a­bly fig­ured it had already been on.

Peter’s a clever scripter, by the way—whenever he posts some­thing on LinkedIn, I make sure to read it, because I know I’ll learn something.

Les­son: Not get­ting error mes­sages in the Log win­dow doesn’t mean your script works.

Why should we care more than “Oops!”?

OK, we all make mis­takes, espe­cially when we’re writ­ing JSL. (We do, right? Please tell me it’s not just me!) No big deal.

So why am I writ­ing about these mis­takes? Believe me, I’m not doing this to make any­one feel bad! They were both great sug­ges­tions, and they looked like they worked. Both Mark and Peter had good rea­sons to think they were correct.

I see some impor­tant lessons for JMP scripters.

Be aware that JMP has a lot of silent error conditions

Mark’s com­mand Show Preferences(Use JMP Locale Settings) didn’t pro­duce any error mes­sages, and it did echo the cur­rent set­tings to the Log, so it was rea­son­able for Mark to assume that putting a spe­cific pref­er­ence as an argu­ment to the com­mand was a valid syn­tax vari­a­tion that would show the set­ting of that spe­cific preference.

This is a type of mis­take I make all the time: I come up with a syn­tax improve­ment in my imag­i­na­tion or in my fail­ing mem­ory, one that isn’t real, and it seems to work, so I think it is real.

But it’s not. There are a lot of sit­u­a­tions where JMP is silent about errors in JSL. Send­ing an unrec­og­nized argu­ment to a com­mand is a typ­i­cal exam­ple. Another is when you put some­thing that needs to be evaluated—say, an arith­metic expres­sion to set a para­me­ter from a global—inside an object mes­sage or plat­form launch com­mand, and it turns out that the par­tic­u­lar mes­sage or com­mand you’re using is not actu­ally capa­ble of eval­u­at­ing its argu­ments. (JMP is incon­sis­tent that way, unfor­tu­nately. Some­times you can get away with it, some­times you can’t, and you can only find out by testing.)

Be sure you know which kinds of errors are silent (and deadly)

This arti­cle would go on for a hun­dred pages if I tried to list every pos­si­ble error and whether JMP will report an error mes­sage or not, but here are some of the most impor­tant things to know:

Some typ­i­cal mis­takes that usu­ally pro­duce error messages:

  • Oper­a­tors that don’t exist, e.g. Until() instead of While()
  • Glob­als that don’t exist or haven’t been assigned val­ues yet (or have had their val­ues cleared with Clear Globals()
  • Using = instead of == as a con­di­tional test, e.g. If(a=1, write("a"), write("not a")) would assign 1 to a, but JMP alerts you to the fact that you prob­a­bly meant to test whether a equals 1 with a==1

Some typ­i­cal mis­takes that do not pro­duce error messages:

  • Mes­sages that don’t exist, e.g. Distribution[1]<<close; instead of Distribution[1]<<close window; just silently pre­tends to run, return­ing the name of the object (in this exam­ple, Distribution[]) as if it had done some­thing to it
  • Argu­ments that don’t exist, e.g. Show Preferences(Initial Splash Window) instead of Show Preferences(All) sim­ply runs as if you hadn’t included the unrec­og­nized argument
  • Scop­ing mis­takes, e.g. inside Formula(), any vari­able that you don’t explic­itly scope, such as by putting :: before a global’s name, is going to be inter­preted as a col­umn name, and unless that leads to an error con­di­tion or some kind of unex­pected behav­ior that you notice, you will never real­ize your mistake
  • Con­di­tional mis­takes, e.g. For() loops or other loops that never need to iter­ate sim­ply don’t iter­ate, and they don’t tell you that they haven’t—unless you write some code to report on the iter­a­tion, e.g. you could set some global inside the For() loop and then check the global after the For() loop

Is it a bug when JMP doesn’t report an error?

Not nec­es­sar­ily.

In the case of invented mes­sages not being flagged, my opin­ion is yes, that should be reported as an error. It’s too dan­ger­ous to think you’re accom­plish­ing some­thing and never real­ize JMP’s just ignor­ing you. But con­sider what the JMP devel­op­ers are up against: JSL is so flex­i­ble that you can send lists of mes­sages like obj << {mesg1, mesg2, …, mesgN}, and you can send mes­sages to an obj << mesg group, and that’s just for starters. It would be hard to make the error-checking and report­ing bulletproof.

What about invented argu­ments? Well, again, I’d say yes, if JMP can’t inter­pret some­thing, it should say so. But again, con­sider what the devel­op­ers are up against: most, but not all, com­mands in JSL can take arbi­trar­ily com­plex expres­sions inside their paren­the­ses. For exam­ple, myList={}; InsertInto(myList, 4) is a straight­for­ward way to put some­thing in a list, but you could also have it cal­cu­late the thing it needs to insert, e.g. myList={}; InsertInto(myList, 1+3). That cal­cu­la­tion could be incred­i­bly com­plex, involv­ing If() tests and For() loops and all kinds of insan­ity. Any num­ber of things could go wrong inside those cal­cu­la­tions. How is JMP’s InsertInto() sup­posed to know that the prob­lem is you made up an argu­ment and not that you attempted a cal­cu­la­tion that didn’t work out?

What about unscoped, mis-scoped, or insuf­fi­ciently scoped vari­ables? Hard to say. The name-resolution and scop­ing details of JSL keep improv­ing with each new ver­sion of JMP, so that care­ful scripters can get bet­ter and bet­ter con­trol of these kinds of prob­lems, but for most scripters (and I include myself), the big­ger prob­lem is that we don’t real­ize when we’re not scop­ing care­fully enough. For exam­ple, inside a Formula expres­sion, any vari­ables that aren’t scoped oth­er­wise will get inter­preted as oper­a­tors (if one by that name exists) or as columns (if one by that name exists). If either of those exist, then the global you thought you were call­ing is not going to come into play. For example:

x=1; column("Age Next Year") << set formula(x + :Age)

That should add 1 to each Age column’s value and put it in the Age Next Year col­umn, right? Right. As long as you don’t also have a col­umn named “x”! If you do, it’s going to add the value in the Age col­umn to the value in the x col­umn and put it in the Age Next Year col­umn; more pre­cisely, it will do that for each row.

Or what about this:

For=1; column("Age Next Year") << set formula(x + :Age)

Will that work? Well, if you want to play along, open good old Big Class.jmp, cre­ate an Age Next Year col­umn, and try these two scripts. First try the one with x=1. That’ll work fine since Big Class doesn’t have an x col­umn. Now try the one with For=1.

What hap­pens?

Do you think it worked? Well, you might—after all, the Age Next Year col­umn has the right answers in it, and you didn’t get an error mes­sage in the Log.

But did it? Let’s check more care­fully. Change the first bit to For=5 and run it again. Do you get val­ues like 17, 18, 19 as you should?

No!

Do you get error messages?

No!

What’s going on? Remem­ber, For is an oper­a­tor! JMP sees a For() oper­a­tor even though its () paren­the­ses and argu­ments aren’t there. Since For has no argu­ments, it doesn’t do any­thing, and then it can’t add :Age to it because there’s no “it” to add it to, so the col­umn is unchanged. JMP doesn’t think any­thing is wrong, because it didn’t see any­thing it couldn’t inter­pret. It knows For()! It just didn’t real­ize that by For what you really meant was 5!

The first time, when For=1, we didn’t real­ize the prob­lem, because the Age Next Year col­umn already had the answers we expected and we didn’t have any way of know­ing they didn’t change because of an error rather than because the answers were the same. The sec­ond time, when we were expect­ing dif­fer­ent answers, we had a chance of notic­ing our mistake—but only if we checked those answers!

What do you do if you actu­ally do want to use a global that shares a name with an oper­a­tor or a col­umn? You scope it. Change it to ::For inside the Formula(), and you’re back in business.

So, it’s not clear that JMP is in the wrong when errors don’t get mes­sages. It’s not nec­es­sar­ily a bug, although it cer­tainly can be frus­trat­ing! We can all hope that these kinds of errors will be trapped bet­ter in future ver­sions of JMP, but in the mean­time, we need to take that respon­si­bil­ity ourselves.

Tips on inter­pret­ing error messages

JMP’s error report­ing is quite help­ful, but it can be frus­trat­ing at times. Here are a few tips:

  • Some­times it’s hard to find the /*###*/ in the Log to see where your error is. Don’t for­get about Edit/Find, or the key­board short­cut Ctrl-F or Command-F!
  • Some­times the /*###*/ will be in a mis­lead­ing place. Start by inves­ti­gat­ing the thing that comes right before it, but if you’re sure that’s not in error, see if you can find some other prob­lem that’s caus­ing that thing to get read in some way other than what you meant, e.g. if you had a comma instead of a semi­colon, it might be get­ting read as the “else” action in an If(), where you meant it to be an extra com­mand in the “then” action.
  • When you get error mes­sages about an unex­pected or a miss­ing ) or , (paren­the­ses or com­mas), if the /*###*/ marker doesn’t help you, try get­ting some help from the Refor­mat Script util­ity. Try refor­mat­ting the whole script, and see where the cur­sor sits after you get the error mes­sage again. If you don’t see the prob­lem nearby, try select­ing chunks of the script and refor­mat­ting those. Keep going until you find the chunk of script that won’t for­mat; your prob­lem is inside that chunk somewhere.
  • Notice that the Refor­mat Script util­ity does help­ful auto-indenting of your code, so that things hap­pen­ing inside other things will be indented fur­ther than their con­tain­ers, and the clos­ing ) will appear vertically-aligned to the com­mand and open­ing ( that JMP thinks it cor­re­sponds to. Make sure that JMP’s align­ment of )s matches what you think you’re doing. Get­ting messed up inside the paren­the­ses is an espe­cially com­mon mis­take when you’re writ­ing cus­tom dis­play boxes.
  • Also try using the fence-matching fea­ture: double-click on a paren­the­sis, and the Script Edi­tor should high­light every­thing up to the match­ing paren­the­sis. See if it matches what you think it should; if it doesn’t, you’ve got one miss­ing or extra some­where. Note: one prob­lem with the Script Editor’s fence-matching is that it won’t always select all the way up to a match­ing paren­the­sis; it’ll just do noth­ing when you double-click. This seems to hap­pen when the chunk is too big—if the match­ing paren­the­sis is scrolled too far off-screen, JMP doesn’t even try. I hope this will be improved in a future ver­sion of JMP. In this sit­u­a­tion, using an exter­nal programmer’s edi­tor (such as jedit on Win­dows or BBE­dit on Mac) can be helpful.

Be sure to test that your code is doing something

The smart JSL mis­takes we saw above didn’t report errors, so the scripters didn’t real­ize any­thing was wrong. This brings up an impor­tant point: you need to test not only that the results you get are cor­rect, but also that you’re get­ting results!

Mark’s error was to invent an argu­ment. JMP’s response was to ignore the fic­tional argu­ment. So how did he know it was a nonex­is­tent argu­ment and not just an undoc­u­mented one? The only way to be sure is to try the com­mand twice—first with­out the argu­ment, and again with the argument—and see if the results actu­ally dif­fer. If they don’t, that argu­ment isn’t doing any­thing. Either it doesn’t exist, or you’re mak­ing some kind of a mis­take in how you’re using it.

Peter’s mis­take was not check­ing that his For() loop actu­ally did any iter­a­tions. Since NArg() was zero, his i counter that started at 1 was never less than zero, so the loop didn’t do a sin­gle iter­a­tion. JMP skipped right over it. There are sev­eral ways to test for this kind of prob­lem. The eas­i­est way is just to put Write() state­ments inside the For() loop that will report what’s going on to the Log win­dow. As long as you make sure to look at the Log and see that the mes­sages you expect are show­ing up, that’s good enough.

But if your For() loop is hap­pen­ing as part of a larger pro­gram, you might not think to look for the absence of a mes­sage, right? In that case it would be bet­ter to lay a trap. For exam­ple, you could set a global right before the For() loop, do some­thing to that global inside the For() loop, and then test the global after the For() loop. For example:

test=0;
For( i=1, i<6, i++,
    [ do whatever you need to do here ];
    test++;
);
if( test==0, throw( "The For loop did not iterate" ));

(Note: it wouldn’t nec­es­sar­ily be a mis­take for a For() loop never to iter­ate. There might come a sit­u­a­tion where you want to write an iter­a­tion test that takes advan­tage of For()‘s abil­ity to decide whether to iter­ate or not.)

What are your issues with JSL errors?

What kinds of silent-but-deadly errors have you noticed? What kind of errors have you found it hard to track down? What tips do you have for trap­ping silent errors, or fig­ur­ing out reported errors?

Please ask your ques­tions and share your expe­ri­ences in the Com­ments below. We’ll be happy to follow-up with replies to your com­ments or at greater length in future blog posts.


Leave a Reply

You must be logged in to post a comment.

Artwork

Global Pragmatica’s art­work includes paint­ings by Zsuzsi Saper and dig­i­tal pho­tographs by Erin Vang. Fur­ther notes on spe­cific pieces of art are given at the bot­tom of pages in which they appear. All art­work is copy­right 2009–2010 by Global Prag­mat­ica LLC®. All rights reserved worldwide.

© 2009-10 Global Pragmatica LLC®

All con­tent © 2009-10 by Global Prag­mat­ica LLC®. All rights reserved worldwide.

Global Prag­mat­ica LLC® is a reg­is­tered trade­mark of Global Prag­mat­ica LLC. The ® sym­bol indi­cates USA trade­mark registration.

Contact Global Pragmatica LLC®

info@globalpragmatica.com
+1 415.997.9671
Oak­land, CA 94611