Refactoring Note: I split off this topic from the topic Public And Private Variables. I leave it to others to revise content from Thread Mode to Document Mode. -- Randy Pearson -- I summarized the conversation and now, hopefully, we've got something we can all hang our hats on-- Steven Black
Most languages, FoxPro included, allow you to pass parameters to routines either "by value" or "by reference". When you pass by value, you are passing a copy of the item to the routine. Pass by value means that if the routine makes changes to the parameter, it happens to the copy and not the item in the calling routine. When you pass by reference, you are passing the item itself, and the routine may modify it.
As an example of the difference, one could ask "Please add the numbers 4 and 5." This would be an example of a "pass by value." Alternately, one could ask "Please add the numbers on the papers in cubbies 15a and 15b." This would be an example of "pass by reference." In the second case, the person doing the addition has the opportunity to modify the data before, during or after acting on it.
In Visual FoxPro, by default, parameters are passed to Procedures by reference and to Functions by value. But VFP has a Set UDFParms setting which controls how parameters are passed to functions. Changing the default setting of
SET UDFPARMS can cause errors which are difficult to debug (see Set UDFParms for why).
Functions and procedures can be expressly passed parameters by reference by prepending @ before the parameter's name during the call. The calling program has call-by-call control over how the parameter is passed by using the "@" pass by reference token. The @ token would be used in procedure calls to alert programmers to the fact that the parameter is expected to change. Example:
SubjectPrice = SubjectPrice * 1.1
IncreasePrices( @itemprice) && changes
IncreasePrices( shippingprice) && does not change
DO ... WITH ...
Procedures, called with the DO ... WITH syntax also can control the passing style of the parameters. By default parameters are passed by reference to procedures, however, enclosing the parameter name in () parens will cause the parameter to be passed by value as in;
DO MYProc WITH Var1, (Var2) where Var1 is passed by reference and Var2 is passed by value.
Objects contents are always passed by reference, though the object reference itself can be passed by reference or value. See Passing Object References for a complete discussion. When passed by value the value passed is a pointer to the object. This causes the properties manipulated within the called routine to affect the object that was passed to the routine. Altering the parameter (a pointer) by setting it to NULL does not release the original object. If the object was passed by reference then nulling the parameter will release the original object (given that there are no other references to the object).
Arrays must be passed by reference, which is the default for calling procedures (DO MyProc WITH ...). Since functions receive parameters BY VALUE as the default, when passing an ARRAY into a function, use the @arrayname format or SET UDFPARMS TO REFERENCE.
|DO procedure WITH variable1, variable2
||DO procedure WITH (variable1), (variable2)
|Variables preceded with @
||Variables wrapped in parens ()
||Possible Object References
|SET UDFPARMS TO REFERENCE
||SET UDFPARMS TO VALUE
There are many issues related to parameter passing. Regardless, it can be said that a good practice for writing any routine that will be called from other places is to assume that the parameters have been passed by reference. Making this assumption will cause you to be very careful about what you do with the parameters being received and can prevent accidental side effects that are really unintended.
Take the VFP functions as an example. The UPPER() function accepts a character string as a parameter, it manipulates that value and returns the string shifted to uppercase, however it does not affect the original string passed into it. Passing by value accomplishes the same result, the called routine can tear apart its parameter without affecting the calling routine.
Thus a routine should never ASSUME that the paramaters passed to it are ALWAYS going to be passed by VALUE. Thus we can simply avoid changing the value of a parameter to prevent the change having unexpected effects in outside programs.
* Do it this way if you want to change the var from the calling program.
* The programmer calling this proc must know to pass by reference.
tcString = UPPER(ALLTRIM(tcstring))
* More code here
* Do it this way if you dont want to change the var from the calling
* program and you don't want to worry about how the vars were passed
lcString = UPPER(ALLTRIM(tcstring))
* More code here
* Even safer...Since parameters may not be passed at all, or
* passed as the wrong variable type, take the above method one more step
* and try this:
lcString = IIF( PCOUNT() < 1 .OR. TYPE(tcString) <> 'C', '', UPPER(ALLTRIM(tcstring)))
IF EMPTY( lcString )
*!* Do some exception handling here
* More code here
Somebody tell me if I am wrong here, but the above suggestion to enter an error routine on a non-user entry fuction or procedure is an unnecessary complication. A programmer using the function is going to get an error message either way if it is not used properly. You do of course have to use data validation on user data entry, but that is completely different from the above example. Note that the code doubles in size, which on a large program, can be a significant slow in execution. Further, even if an error is raised by an exception, there is nothing an end user is going to be able to do with it. Thus it is unnecessary.
Isn't this the purpose of an ASSERT? You use the ASSERT to make sure your function is being called properly during development and then set asserts off and compile the code without the now unnecessary error checking.
Passing by reference or value is part of the routine's contract and in the majority of utility function cases, we want the function to not clobber parameters.
If you pass by reference you expect the parameter item to change. If we always code to defensively disable or ignore pass-by-reference, then we limit the flexibility (and brevity) of calling routines, not to mention handcuff the return value to being the fruits the function, which leads to difficulty in errorhandling (since you can't ever return error codes) and possibly many other things.
Thus if you're talking about standards and conventions, making the assumption that all PARAMETERS have been passed by reference is the safest way to code inside the receiving routine.
That's a good reason to differentiate between a function and a procedure in your code. Use procedures to manipulate the parameters and functions to return values.-- Mikel Moore
But you can't generalize it quite this simply. There are legitimate needs for functions that return more than one value, or at least have the option to do so, and these functions must manipulate (at least one of) the parameters. -- Randy Pearson
Does passing by reference break ?Encapsulation Certainly pass by value protects encapsulation, but it's probably not correct fair to say that passing by reference breaks encapsulation.
For example, this wiki's parsing engine uses pass-by-reference extensively, and the different parsers (topic links, http, mailto, Amazon, kbase, tags) are none the less encapsulated for it.
Hey Randy, how about an APARAMS() function that populates an array with information like datatype, isnull, byref/byval, etc?
<\subliminal-request> -- David TAnderson
One way that I specifically document reference parameters is to use R (reference) as it's scope prefix instead of P (T if you use the CodeBook convention). Doing this warns me of the responsibility I have inside the function and helps remind me what parameters are to be sent with the "@" pass by reference token.
function AltersAParameter ruAlteredParameter, roCallerObject, pcValueParm
ruAlteredParameter = roCallerObject.Name
roCallerObject.Top = 1000
I think the cautions to respect the possibility of parameters passed by reference are an additional argument in support of a practice I've long followed of never (except with malice aforethought) modifying a parameter. If the parameter needs to be modified in some way, I store it to another memvar before doing the dirty deed. I've always followed this practice so that if I need to refer to the parameter in multiple places in the routine, I know that it's still "pristine" and unchanged from what was originally passed. -- Steve Sawyer
The following code has to do with passing arrays. There are several points to note:
1. Passing nothing - If you pass nothing, the parameter argument is FALSE, so the code must test for an array and respond appropriately.
2. Passing by Value - This simply doesn't work. VFP only sends the first element of the array (e.g. "One" in the example).
3. Passing NULL - This works the same as #2 above.
4. Passing a single value Vs array - Ditto for #2.
5. Passing a by-ref array that is too small - The receiving array either needs to adjust or the sender needs to get it right.
6. Passing a by-ref array that is too big - Ditto as #5.
7. Ahhh! This one is JUST RIGHT! - Passing a by-reference array that is dimensioned perfectly with the receiving method.
NOTE: Thank to David Frankenbach above for the excellent suggestion of using "r" to indicate a by-ref value in the supplier method. Also, Steve Sawyer and I are on the same page where passed in arguments are copied to LOCAL variables, so the arguments remain pristine and can be reviewed in DEBUG for testing purposes.
SET ASSERTS ON
LOCAL loTestObject as MyTest OF test_array_argument.prg
LOCAL ARRAY laMyArray
LOCAL ARRAY laMyNextArray
LOCAL ARRAY laMyOtherArray
laMyArray = "One"
laMyArray = "Two"
laMyArray = "Three"
laMyArray = "Four"
laMyArray = "Five"
laMyOtherArray = "Blue"
laMyOtherArray = "Red"
laMyOtherArray = "Green"
laMyNextArray = "Cat"
laMyNextArray = "Dog"
laMyNextArray = "Mouse"
laMyNextArray = "Fish"
laMyNextArray = "Bird"
laMyNextArray = "Frog"
loTestObject = CREATEOBJECT("MyTest")
loTestObject.MyMethod() && Case 1 - Passing nothing at all.
loTestObject.MyMethod(laMyArray) && Case 2 - Passing by value.
loTestObject.MyMethod(NULL) && Case 3 - Passing NULL (like method #2).
loTestObject.MyMethod("Hello") && Case 4 - Passing a value (like method #2).
loTestObject.MyMethod(@laMyOtherArray) && Case 5 - Passing too small.
loTestObject.MyMethod(@laMyNextArray) && Case 6 - Passing too big.
loTestObject.MyMethod(@laMyArray) && Case 7 - Passing by reference (Correct Way)
DEFINE CLASS MyTest as Custom
loException as Exception
LOCAL ARRAY raMyArray
TRY && We don't know what to except from taMyArray -- NULL, value or ARRAY
ASSERT (( ALEN(taMyArray) == ALEN(raMyArray) )) MESSAGE "check: Array dimensions must match."
CATCH TO loException && If we get here, then we do NOT have an array!
STORE NVL(taMyArray, "N/A") TO raMyArray
? "1.", raMyArray
? "2.", raMyArray
? "3.", raMyArray
? "4.", raMyArray
? "5.", raMyArray
WARNING: Any PUBLIC variables (usually assumed to be global in scope) that are passed by REFERENCE get HIDDEN in the called procedure OR function. See Disappearing Public Vars for more details.
Contributors: Jahn Margulies, Steven Black, Randy Pearson, David TAnderson, Jim Booth, Pamela Thalacker
See Also: Naming Conventions Variables, Public And Private Variables
Category Naming Conventions Category Parameters