I was taught an idea called “Programming by Intention”. I expect I’ll be writing about it a lot as I write about scripts, because I try always to work this way. Today I just want to introduce the idea.
If you’ve done scripting or programming you’ll probably recognize this experience. I got this thought example from a talk I heard in Ann Arbor a couple of years ago. Have you ever been looking at a big long blob of script or program code, trying to figure it out? And then suddenly you see the purpose of a few lines right in the middle of things. You probably almost cry out “I see what he’s doing here!!” He’s popping up and down!!
if ( llGetPos() == pos1 ) {
llSetPos(pos1 + <0.0, 0.0, 1.0>);
} else {
llSetPos(pos1);
}
When that happens, we have just figured out the intention of that little bit of code. What should we do about it? Well, at the very least, we could put in a comment saying whatever the code is doing. I was taught that that’s not good enough. Comments get out of date. They rot and die. And they are not necessary: we can script without them.
I was taught to take those few lines and extract them out into a function with a name that says what they do, like this:
popUpOrDown() {
if ( llGetPos() == pos1 ) {
llSetPos(pos1 + <0.0, 0.0, 1.0>);
} else {
llSetPos(pos1);
}
}
Some good things happen when we do that. First of all, now the code itself says a little better what it means. We don’t have some additional document, or comments, saying “OK, what this hard to understand stuff does is this or that.” Instead the code itself says what it does. I like that a lot better.
But two other things happen that I also like. Second, I find very often that I have use for that function again! Instead of copying and pasting the code, I just use the function. How neat! And I didn’t even have to plan for it, it just seems to happen very often.
Third, once I get into the habit of doing things with little bitty functions with names that mean something to me, I can actually start programming that way from the beginning. Let me sketch an example. Last night I wrote a tiny script that made a cube pop up and down when you clicked it. I was just showing off for a friend. Now that is such an easy script that I just wrote it straight out. To write it by intention, I might have done something like this:
default {
state_entry() {
}
touch_start(integer total_number) {
bounceUpOrDown();
}
}
Now I could have called that “bounce” or something shorter. I wanted to say as much as I reasonably could about what I intended, though. I was aware that what I was going to do was to make it bounce up on one click, and back down on the next. So I said “bounce up or down” to remind myself now, and later when I’m reading, what I meant to do.
So far, that program won’t even compile yet: there is no bounceUpOrDown defined. I like to get the script functioning as soon as I can, not working all the way of course, but at least showing me that things are moving along. So I might add this next:
bounceUpOrDown() {
llOwnerSay("bouncing");
}
default {
state_entry() {
}
touch_start(integer total_number) {
bounceUpOrDown();
}
}
OK! Now when I click my little object, it says “bouncing”. Notice that I used llOwnerSay(…) instead of llSay(0, …). That’s a much nicer way to go when you are scripting in a public sandbox. llSay bugs everyone. llOwnerSay only talks to you.
So now the script is working, it just doesn’t move the cube up and down yet. What about that? Well, here’s my intention. I plan to have the object remember where it starts. When you click it, if it is where it started, it goes up by 1 meter. If it is not where it started, it goes back to where it started.
This is an easy script, and of course I could just type it in. But even for the easy ones, I seem to get better results if I continue with intention. So the next version might look like this:
bounceUpOrDown() {
if ( atStartPosition() ) {
bounceUp();
} else {
bounceDown();
}
}
default {
state_entry() {
}
touch_start(integer total_number) {
bounceUpOrDown();
}
}
Can you see that this code expresses just exactly what I intend to do? I hope so. So this reminds me of what I’m up to, even as I’m building … but it will remind me of what I was up to when I come back to the program a long time later.
OK, what’s next? Well, how about elaborating on what bounceUp and bounceDown are, like this:
bounceUpOrDown() {
if ( atStartPosition() ) {
bounceUp();
} else {
bounceDown();
}
}
bounceUp() {
llSetPos(startPosition + oneMeterUp);
}
bounceDown() {
llSetPos(startPosition);
}
default {
state_entry() {
}
touch_start(integer total_number) {
bounceUpOrDown();
}
}
Can you see now that the logic of the program is right in front of us? I hope so. I’m a bear of very little brain, so I find this a very useful way to script. Now I wouldn’t do this simple program this way, but as I look at this and how clear it seems, I think maybe I should. Anyway, we are left with just two little problems, defining and initializing startPosition and oneMeterUp. That goes like this:
vector startPosition;
vector oneMeterUp;
bounceUpOrDown() {
if ( atStartPosition() ) {
bounceUp();
} else {
bounceDown();
}
}
bounceUp() {
llSetPos(startPosition + oneMeterUp);
}
bounceDown() {
llSetPos(startPosition);
}
default {
state_entry() {
startPosition = llGetPos();
oneMeterUp = <0.0, 0.0, 1.0>;
}
touch_start(integer total_number) {
bounceUpOrDown();
}
}
Oops! Did you see my mistake? As soon as I tried to save the script, it told me that atStartPosition() is still undefined! Silly me. What I like about this approach, especially for more complex problems, is that the compiler often tells me, as it did here, just what the problem is. And it is easier to be told what’s wrong than to have to figure it out. Especially if you’re like me, totally sure that your program must be correct! OK, let’s provide atStartPosition, like this:
vector startPosition;
vector oneMeterUp;
bounceUpOrDown() {
if ( atStartPosition() ) {
bounceUp();
} else {
bounceDown();
}
}
integer atStartPosition() {
return llGetPos() == startPosition;
}
bounceUp() {
llSetPos(startPosition + oneMeterUp);
}
bounceDown() {
llSetPos(startPosition);
}
default {
state_entry() {
startPosition = llGetPos();
oneMeterUp = <0.0, 0.0, 1.0>;
}
touch_start(integer total_number) {
bounceUpOrDown();
}
}
Now we’re done, I think. And sure enough, we are. Let’s summarize what happened here. I had the intention to build a script that made a box bounce up and down. I expressed that intention by making up a function that I hadn’t yet written, bounceUpOrDown, and then writing it. I put a little display statement in it as a sort of test.
Then my intention was to have the bounce function test to see if we are at our start position. If so, we would go up one meter. If not, we would go back to the start position.
And that is exactly what the code says. For me, this code expresses all the ideas I had while I wrote it, and it doesn’t even need any comments to help out. This is the way I always try to work, and it seems to make things go much more smoothly. So I offer the idea to you, as it was offered to me a few years ago. Try it, see if you like it, and let me know!
Bye for now!
Hello Ms Janet.
As I was reading through the code snippet I saw comparisons checking to see if two vectors were equal. It struck me as odd since equality with real numbers can be problematic. I usually try to see if real numbers are close. For example check if
llVecMag(llGetPos() – pos1) < 0.001
or some such approach. I realized, though, that sometimes I do check for equality in LSL simply from being in a hurry. The odd thing is that it usually works out okay.
I wonder now if the LSL compiler automatically checks this. It does not look for equality between reals but automatically checks to see if they are within some preset tolerance. It might be interesting to test this.
Sincerely,
Grandma Bates
You’re right to be concerned, Grandma. I thought about the comparison when I wrote the thing and decided that it was safe in this case, since it will just keep trying to go to startPosition if it isn’t there, and since one has done llSetPos(startPosition), llGetPos() is likely to have exactly that value.
In the interest of exposition, I decided to stop there, but I do think one should really do it the other way. Maybe someday I’ll have a little library of useful functions to save time and make that more clear. Or maybe they’ll even give us a library capability!
Thanks!
Very helpful for those, like me, with no programming experience, to see this process explained in steps, Janet.
Yay Janet! I do that too! I find that I prefer to be *very* verbose in my code (and comment tonnes and tonnes) but 6 months later I still understand what I was trying to do. This is a great starter article, and I look forward to reading more. You are a scripting Valkyrie! :)