[Scope problem] AppleScript has this really annoying variable issue…

You know what I hate the most about AppleScript?

It’s variables have to be objects or integers. It’s impossible to just take a varibale (like the result of a save panel) and drop it into another variable.

Example:

on panel ended theObject with result withResult
	if theObject is the save panel then
		if withResult is 1 then
			set saveOut to (path name of save panel)
			set saveTrue to 1
		else if withResult is 0 then
			set saveTrue to 0
		end if
	end if
	if saveTrue = 1 then
		display dialog saveOut
		downloadFunc()
	end if
end panel ended

set saveAs_after to "'" & saveOut & "'"

I just get the error: saveOut is not defined, and yet it was display one second ago in a dialog! ARGH! Am I not allowed to use the variable for more than one thing? I’ve tried shunting it into another variable before combining it with the other text.

No luck.

What’s going on??

I’m guessing that your “downloadFunc” subroutine doesn’t know what you’re talking about, because “saveOut” is just a local variable.

Please, do tell… :cool:

Well, they have to be something. You should try writing in… well, pretty much any other language, in fact. Applescript is actually relaxed when it comes to variables and does a good job figuring out what you’re trying to do when you don’t even know what you’re trying to do. You can sometimes get applescript to compare two variables or accept an assignment without casting incompatible data to another predefined type. Most other languages error out at compile time when trying to set a variable without explicitly declaring it’s type and then setting it’s value with ONLY data of a valid type. No other language is so forgiving as applescript.

As Bruce pointed out, it’s probably not a variable class issue, but rather a scope issue. The value of ‘saveOut’ is discarded when the panel ended handler is finished running. This is what Bruce means by “local”. Variables you define only within a handler and not globally are unique and have meaning ONLY within that handler. In fact, you can have a hundred different subroutines and handlers using the same variable name, and they will never know that the other’s exist. In the head of your script, just add the following line and you may be golden.

property saveOut : ""

By adding this one simple declaration, ALL instances of the variable saveOut anywhere in the script reference the same, single, global instance of saveOut. Just be careful to take this new global persistence into consideration, and make sure to set it’s value back to an empty string when you’re definitely done using it so as to avoid getting the previous run’s data if your code error’s out or fails in subsequent executions.

j

Nonsense. I assume the error you’re talking about occurs in the downloadFunc handler (you don’t provide enough detail to be sure), in which case it’s a variable scoping issue. AppleScript != 1970s BASIC. You need to provide the value to downloadFunc in some way. Can’t provide definitive advice without seeing the full program (good application design is a topic all of its own), but you’ve basically got two options:

  1. If this value is only used by downloadFunc and downloadFunc is called in this handler only, then pass it as a parameter to downloadFunc:
downloadFunc(saveOut)

...

on downloadFunc(pathname)
   -- do stuff with the value in variable 'pathname' here
end
  1. If other parts of the program will use this value/call downloadFunc() at a later date, you’ll need to store it in a property instead as jobu describes. (Avoid using properties indiscriminately though, as they increase the complexity of your code and make it harder to read and modify.)

As with most things, it helps to RTFM. Typically unreadable ASLG section on variable scope is here, or you could buy one of the various dead-tree books on AS and read that.

If that’s supposed to quote a path for use in a shell script, it’s completely unsafe - if the string in saveOut contains any single quotes all sorts of very nasty things could happen. Never do it. Instead, use ‘quoted form of saveOut’ which will do it correctly.

Oh yeah… I remember global and local variables from my C++ classes.

Hehe… duh. Me very very stupid. :smiley:

hhas, I agree that there’s not really enough data to support any particular solution… only recommendations. A common obstacle to providing valuable feedback is the lack of information upon which to base any claims.

I’m not sure what your definition of “indiscriminately” is. In plain scripts and applets, the need for properties is limited, as these types of endeavors typically are single-run, single-action types of tools that have no need for persistence of data. In ASS, however, some of my projects have many dozen properties. I find it more confusing sometimes to write handlers which accept (and inevitably must return) many parameters than it is to simply create persistent containers (properties) that I can reference from anywhere in my script at any time. Typically, I feel that the only time one should pass a variable to a handler is if that handler a) acts upon or modifies that variable, b) requires that variable or reference for reasons of scope, c) there is some other circumstance that requires it. After a while, you begin to understand whether your variables are application-level constants or whether they are temporary variables for use ONLY in handlers and subroutines. It gets rather complex managing variables when you have multiple-layer data manipulations or nested subroutines that you must repeatedly pass variables through as parameters. It is much simpler… and much more in line with the obj-c roots of AS… to simply create a property variable and use that to reference the particular data. This also has the benefit of ensuring that at no point are you risking allocating multiple instances of the same data… i.e. you’re always working with the same data and the same variable instance.

A bit off topic, but… :wink:
another coding technique that I use much of the time, is to create my own naming convention for my variables. My properties and primary application variables are named plainly, clearly, and verbosely. For example, I see a lot of sample code that contains variable names like “cID” or “tFld”. (Note: I didn’t see vars like these in this thread, I only thought this would be a good time to say something about the topic). Naming items like this doesn’t have any effect but to confuse the writer and add work to the development process. I realize that people often do this just for their test scripts, but it doesn’t help there either… and it makes understanding posts more difficult for those of us who have no idea what a script does at first glance. Shortening names does not provide any file size or compilation advantage, so why use cryptic names when you can simply spell out what you’re trying to say? In my code I use clear variable names like “currentUserID” or “textField_UserName”. ‘Where am I going with this?’, you ask. I use a different naming convention for my local-scope variables than I do for application vars. In the name of clarifying code, I evaluate every variable I create and name it so as to tell me as I read through my code: what it contains, whether it is available to me outside the current handler, and whether I need to worry about it after I’ve used it. For example consider the following creation…

property currentUserName : ""

on clicked theObject
	if name of theObject is "updateUserName" then
		set tmpCurrentUserName to content of text field "userName" of window "mainWindow"
		set tmpNewUserName to modifyUserNameToMySpecs(tmpCurrentUserName)
		updateCurrentUserName(tmpNewUserName)
	end if
end

to modifyUserNameToMySpecs(tmpCurrentUserName)
	set tmpCurrentUserName to (tmpCurrentUserName & " (Cool dude)")
	return tmpCurrentUserName
end modifyUserNameToMySpecs

to updateCurrentUserName(tmpNewUserName)
	set currentUserName to tmpNewUserName
end updateCurrentUserName

As you can see, I’ve passed some variables around between handlers and subroutines. You don’t need to be shy about using the same local variable name in multiple handlers, because their scope ALWAYS ends at the end of the handler. As hhas says, by passing the variable as a parameter, the data is brought into the subroutine, but it’s not retained anywhere once the subroutine ends. The key is noting the naming of the variables. It should be clear when reading the whole script what everything does. Variables are clear and named according to what they contain. All local variables are denoted by a “tmp” prefix, so (unless you stray from your convention) you can be sure that they are only temporary and they do not influence any other instances anywhere else in the script. Subroutines are clearly named, and say in their name what it is that they do. Also, take care to think ahead and name your IB objects according to a similar convention. Using “name” as a text field name or “button” as a button name may not be entirely intelligent. Instead, use “textField_Name” or “mainSubmitButton” instead. That way you’re never scrambling to remember what a reference means later, and your code flows like a story rather than looking like a mess of “death-code”. That’s one of the benefits of applescript, and scripters should embrace that luxury… as you won’t find it anywhere else.

Smart code starts with smart programming. Try to create your applescripts to kind of “tell a story” as I mentioned, rather than as a cryptic jumble of confusing syntax. Applescript affords us the flexibility to be forceful, elegant, and practical with our code all at the same time. By being open to this, it becomes clear that many of our obstacles are self-induced, and that the answers are usually always right in front of us.

j

Jobu,

Good stuff as always. I agree wholeheartedly about good variable naming conventions. As one who used to use cryptic variable names, I learned the ‘hard way’ about getting tripped up on my own code! My only point of contention is with the following:

While I agree that using names like “name” and “button” is not a good practice, using names like “textField_Name” and “mainSubmitButton” leads to a lot of duplicate typing:

      set currentUserName to the contents of text field "textField_Name" of  window ...

Instead, I try to name my IB objects so that the read well in code:

      set currentUserName to the contents of text field "userName" of window ...

Thanks for the great info. I know you’ve helped me a great deal with my code. Keep up the great work!

Brad Bumgarner, CTA

Urrrh, trying to drag me into writing a big long essay on how to do application design.

OK, quickly: “indiscriminately” means “done at random or without careful judgment”. I didn’t go into details because to understand those requires some knowledge and experience in both language features and general programming concepts. Good programming rules differ between simple batch-processing applets or Studio-based wrappers around shell script commands (which are generally batch-processing scripts in disguise), and “real” desktop applications where they get considerably more complex and involved.

The typical Studio-based shell script GUI wrapper consists of a window containing a bunch of text fields and checkboxes, plus a ‘Do It’ button. When the ‘Do It’ button is clicked, the receiving event handler extracts all the data from the window’s text fields and checkboxes, generates a complete shell script and passes it to ‘do shell script’ or terminal to execute. The user’s data lives in the GUI until it’s time to use it, and since there’s only one part of the program that uses it the simplest solution is for that part to fetch the data from the GUI directly when it needs it. This approach is simple and works fine in those sorts of limited circumstances, so as with straightforward batch scripts there’s no reason to have lots of global state kicking about.

In a real desktop application, the user’s data doesn’t live in the GUI. It’s stored and manipulated in the heart of the application, and what’s displayed in the GUI is merely a reflection of that data, with the two kept in sync. At this point you have to use properties to hold that user data, plus any private state the application needs to control other aspects of its behaviour (e.g. maintaining a ‘has document changed’ flag so it knows if it should display a ‘save before closing?’ dialog when the user clicks a window’s ‘close’ widget).

That said, well designed “real” desktop applications keep the amount of global state to a minimum as well, using a heavily modularised (typically object-oriented) design that restricts properties’ scope to only the program components that use them. For example, a document-based Studio application might have a one top-level property containing a list of ‘MyDocumentData’ script objects, and another top-level property containing application-wide preferences settings. Each MyDocumentData object will have a property containing the id of the window that displays its data (the only GUI-related content it has), plus other properties containing the user data for that document, plus various handlers for manipulating that data. And then a separate bunch of glue code is responsible for keeping windows and MyDocumentData objects in sync.

So yes, properties are used more often in more complex application designs, but the rules governing their proper use are just as well-defined and strict about where and when they apply as before, and the end goal is still the same: to keep variables’ scope limited to the smallest area possible.

But anyway, if you’re going to be writing that sort of program, you’ve hopefully read some general literature on application architecture, design and construction, in addition to AppleScript-specific literature that describes the language features available to you and general literature on basic programming techniques so you know how to use those features effectively. (I find a decent AppleScript book, plus a high school-level Computing Science textbook and a copy of ‘Code Complete’ takes care of the last two points very nicely.) In which case, you’ll already know all about how to manage variable scope and all the rest and won’t be fazed by such vague handwaving as in my original post. Or, of course, you can follow the time-honoured path of skipping all that slow and tedious book larnin’ and just hack out a big ball of mud and beat on it awhile till it more or less behaves as intended. [1]

Better go and get on with some proper work now…

has

[1] Yes, sarcasm intended, though while purists will balk it’s an approach not without its merits: while it will bite back sooner or later, one can still often get a fair distance despite that. And if you’re not writing serious apps on a regular basis, the amount of time and work it’d take to master all the professional techniques probably wouldn’t be worth the investment anyway.

Some commonly used rules are:

  1. Variable names always start with a noun (e.g. ‘nameCount’), command names always start with a verb (e.g. ‘countNames’).

  2. Constant [1] names are always prefixed with a ‘k’ (e.g. ‘kLinefeed’).

  3. Property names are always prefixed with a ‘p’ (e.g. ‘pNameCount’). [2]

Plus, if you’re using libraries or object-oriented programming:

  1. Private [3] property and handler names are always prefixed with an underscore (e.g. ‘_nameCount’, ‘_countNames’).

You’ll find these and very similar rules are often used in other languages, so there’s obviously something to be said for them in terms of familiarity and ease of use. [4]

HTH

has

[1] i.e. Properties whose values are predefined and must never change at run-time. (AS provides a few: ‘pi’, ‘return’, etc., but you can also define your own constants to avoid ‘magic number’ syndrome. AS doesn’t allow read-only-ness to be enforced programmatically, so you enforce it through convention instead.) Another common scheme is to write constant names as ALL_CAPS, though that can get rather heavy on the eyes.

[2] Or ‘g’ for global. Though polite ASers always use properties, not globals, anyway, and ‘p’ seems a bit more natural for those.

[3] i.e. Properties and handlers only for use within the object they’re declared. Again, AS can’t control access itself, so you do it via convention.

[4] And then there’s all sorts of bizarre crazy overkill systems like Hungarian Notation as well, but you don’t really want to go there. :slight_smile:

Thanks for the convention ideas, hhas. I think I’ve been using #1 for a long time without realizing it (all part of that ‘readability’ thing). I may just have to start using the other ideas in future projects.

Thanks for the info,
Brad Bumgarner, CTA

I’m certainly not looking for that. I asked what “your” definition was. Not “the” definition. I feel that everyone, including you and I, always have more to learn… so opening the door for more dialog didn’t seem inappropriate. But instead you’re preaching to the choir, even though I agree wholeheartedly with your comments.

j

Don’t worry, just pulling your leg there. Last week somebody asked on comp.lang.python for an explanation of why some sample GUI widget code was written the way it was and got an entire essay [1] in return. Actually rather proud of that one; I was on a real roll that day. (Pity I can never seem to translate these occasional bursts of inspiration into a full blown honest-to-goodness-people-pay-me-for-this work of literature, but my loss is others’ gain.)

And where’d you think all my vast knowledge and expertise comes from? :slight_smile:

All programming is a learning process. Fortunately, a lot of the really boring basic principles and techniques have already been figured out by other, much smarter folks, so all the rest of us have to do is steal that knowledge from them and then we can get on with all the interesting stuff that we really want to do. There are some jolly deep holes out there awaiting the unwary (and I say this as one who’s dug their way into most of them at one time or another) so why wait to discover them the hard way?

Probably the hardest part is following what they’re saying since a lot of it appears to be in impenetrable Jargonese. Let’s face it: were we natural nerds we probably wouldn’t have picked AS as our first language. If/when the programming bug well and truly bites, however, and things like values, variables, loops and conditionals start making a reasonable bit of sense, a bit of that background reading does start to be a useful investment if you intend to do more. It’s tough to get into at first, but the more you do it the easier it gets.

You callin’ me stupid? Huh? Huh?

has

[1] Best read if/when you ever get to that point of trying to understand the MVC design pattern “because it’s what all the real applications use” and are getting honked off because the explanations you’ve read so far still haven’t told you anything about why you’d want to do it. But until/unless you’re at that stage, don’t bother as it doesn’t provide much technical background and won’t make much sense without it.

Genuinely useful, has - thanks for the link to your MVC essay.