Chapter 7: User Defined Functions

Working with Variables

In this chapter, we will explore ways of using the value assigned to a LOCAL variable created in one FUNCTION in a different FUNCTION. The concept of passing a variable is an important one which you will use increasingly from this point onwards.

The second area we will explore is how the value of a LOCAL variable created in a FUNCTION can be modified elsewhere using the RETURN statement to - you guessed it - return a value to the original FUNCTION. This again is an important concept to understand and also one that you will use a great deal in your own programs.

The third area discussed is internal documentation. This is the capability to add 'plain English' comments to explain what your code is trying to do and it is a powerful way of making future enhancements or changes simpler to do. Imagine that you have a program comprised of several thousand lines of code and you want to change one small part of that. By having internal documentation, you will be able to easily understand some of the more complicated code by including some useful comments to explain it without having to read and 'unpick' the meaning of the actual code.

The fourth subject I will introduce is another type of memory variable – the PUBLIC variable. I personally try to avoid using this type of variable in my code as using them can lead to your program behaving in unexpected ways, especially when you have a large and complex program developed over many months, but it is important that you understand this type of variable as there may be times you consider them to be the best 'tool' to use.

The final subject in this chapter will introduce you to an essential aspect of programming – debugging! I would be quite surprised if you have not already experienced problems compiling and running your code and I hope you have been able to get around this by double checking the code examples listed at the beginning of each chapter to ensure you have entered the code exactly as it is listed! To start understanding debugging, we will deliberately introduce some bugs in your code so that you can see what happens when you compile and run the program. By creating these bugs, we can start to explore how these errors are displayed and understand steps we can take to try and resolve the problem.

The 'Bug Free' Code

Use your chosen editor, to enter the code exactly as below and save the file to the MySourceCode folder naming it HMGGuide-Ch7.prg. Then compile the program using the "..\build HMGTuror-ch7" command.

  #include "hmg.ch"

  FUNCTION Main()

  LOCAL cMainWinTitle := "ePortfolio"
  LOCAL cBtn1Cap := "Button 1"
  LOCAL nBtn1Row := 32
  LOCAL nBtn1Col := 64
  LOCAL nBtn1Wide := 60
  LOCAL nBtn1High := 100
  LOCAL lUsrResp := .F.

  DEFINE WINDOW Win_1 ;
    AT 0, 0 ;
    WIDTH 760 ;
    HEIGHT 640 ;
    TITLE cMainWinTitle ;
    WINDOWTYPE MAIN

    DEFINE BUTTON btn1
      ROW nBtn1Row
      COL nBtn1Col
      WIDTH nBtn1Wide
      HEIGHT nBtn1High
      CAPTION cBtn1Cap
      PICTURE "c:\hmg.3.4.0\MySourceCode\Icons\IconCh4.bmp"
      ACTION lUsrResp := UDFbnt1Clicked(lUsrResp)
      END BUTTON

  END WINDOW

  CENTER WINDOW Win_1
  ACTIVATE WINDOW Win_1

  Return Nil

  /* -- This is a separator line -- */

  FUNCTION UDFbnt1Clicked(lUsrRespVAR)

  LOCAL lResponse := .F.

  // The variable lUsrResp is a LOCAL variable in FUNCTION MAIN().
  // The value was passed to this function and is now called
  //   lUsrRespVAR which is LOCAL to FUNCTION UDFbnt1Clicked
  // The following code will ask the user if they want to keep or
  //   change the value of lUsrResp in FUNCTION Main()
  // The value of lUsrResp in FUNCTION Main() is FALSE the first
  //   time it runs
  // The users response will be stored in a LOCAL variable in
  //   function UDFbnt1Clicked which is then returned to FUNCTION Main

  IF lUsrRespVAR
      lResponse := MsgYesNo("lUsrResp is TRUE. Keep it?", ;
      "Keep It")
   ELSE
      lResponse := MsgYesNo("lUsrResp is FALSE. Change it?", ;
      "Change It")
  ENDIF

  RETURN lResponse

You can download a copy of the source code file from here - HMGGuide-Ch7.prg

When you have compiled the program, run it a couple of times and note down what happens depending on what you do when asked to either keep or change a value after you click on the picture button. Make sure you try both of the options; to keep or change the value!

Understanding Passed and Returned Variables

Before we start, remember that we are only exploring the changes made to the code since the last chapter. These changes have all been highlighted in yellow and you should refer to previous chapters to refresh your memory about any of the other code included in this chapter such as the IF... ELSE... ENDIF command.

LOCAL lUsrResp := .F.

The first change to the code is to declare a new LOCAL variable in FUNCTION Main() and assign it a default value of false (.F.).

ACTION lUsrResp := UDFbnt1Clicked(lUsrResp)

The simplest way to understand the changes made to the ACTION property of BUTTON bnt1 is to look at this line from right to left!  Notice that the UDF UDFbnt1Clicked now includes LOCAL variable lUsrResp between the two brackets at the very end of the line of code? Whilst you have already been passing variables to other functions such as MsgBox(), it is important to understand that when you pass a LOCAL, variable from one function to another, you are actually passing a copy of the value of the variable rather than the variable itself because a LOCAL variable is only visible in the function in which it is declared.

So, if we are only passing a copy of the value assigned to the LOCAL lUsrResp variable to FUNCTION UDFbnt1Clicked(), how do we change the value actually assigned to lUsrResp within FUNCTION Main()? That is done by using the value that UDFbnt1Clicked() will return when it finishes running by stating lUsrResp := just before UDFbnt1Clicked(). The two symbols represent an operator which instructs the program to do something to the variable expressed to the left of it. In this case, reading right to left, we are instructing our program to send a copy of the value contained in lUsrResp to UDFbnt1Clicked() and, when that finishes running, assign (:=) the value returned by UDFbnt1Clicked() to lUsrResp. This section may take you a few minutes to understand! Keep looking at this line of code and try reading it from right to left until it makes a little more sense.

FUNCTION UDFbnt1Clicked(lUsrRespVAR)

The next change we have made is to 'tell'  UDFbnt1Clicked() about any variables it will receive when it is run. This is done by giving each of the variables it receives a name, separated by commas, which are automatically created as a LOCAL variable within the UDF. In this code, I have called the variable lUsrRespVAR, which will contain the copy of the value originally stored in lUsrResp in FUNCTION Main(), when it is passed to this UDF.

lResponse := MsgYesNo("lUsrResp is TRUE. Keep it?", ;
   "Keep It")

The program now uses the IF lUsrRespVAR logic command to decide which code to execute depending on the value contained in lUsrRespVAR. When that value is true, this is the line of code that is executed and, reading right to left, we can see that the pop-up control MsgYesNo() will be run and the value it returns will be assigned (:=) to lResponse.

lResponse := MsgYesNo("lUsrResp is FALSE. Change it?", ;
   "Change It")

This is the code that executes when lUsrRespVAR is not true (i.e. it is false). This will be the case the very first time the code executes as we gave a default value of false to lUsrResp when it was originally declared in FUNCTION Main(). As above, the pop-up control MsgYesNo() will be run and the value it returns will be assigned (:=) to lResponse. Notice also the subtle differences in the wording of the two questions? This was done deliberately to make the code simpler to write and explain.

RETURN lResponse

The final statement in the UDF is its 'closing' statement which instructs the program to return a copy of the value contained in lResponse to the function that called it. Again, remember that lResponse is only visible in UDFbnt1Clicked and the value is needed since we will be assigning the returned value to lUsrResp in FUNCTION Main().

More on Internal Documentation

Ok, what about the section I skipped over; what are all of those lines that start with the two // symbols? I'm sure you have worked out very quickly that these, like the combination of /* and */ are used to demarcate comments the author of the code has written and these are also ignored by the compiler. In the case of //, this directs the compiler to ignore anything that follows to the right of the symbols on the same line whereas everything is ignored after /* until the compiler sees the */ symbols.

As I previously stated, internal documentation is a very useful way of explaining the more complex parts of your code to someone else that might read it. You will also find in future that it even helps you when the time comes to change parts of your own programs which you might have written a very long time ago! Obviously, the use of internal documentation is a  balancing act and the reality is you will eventually only need to use this to explain the more complex code you write rather than use it for each and every line. For now, it is useful practice for beginners to use this as much as possible to explain new functions, commands and other language capabilities until your knowledge has increased sufficiently enough not to need it for the more common parts of your code.

Introducing PUBLIC Variables

You already know what a LOCAL variable is and how to use it. Hopefully, you also know that these can only be seen and used within the function where they are created. I previously suggested that LOCAL is not the only type of variable and I now want to introduce a new, different type, the PUBLIC variable. These are different in that these are visible to all parts of your program once they have been declared and can therefore be changed by any part of your program!

To explore the PUBLIC variable, use your chosen editor to enter the code below and save the file to the MySourceCode folder naming it HMGGuide-C7a.prg then compile the program using the "..\build HMGTuror-ch7a" command.

I have included internal documentation in the code file to explain the changes, so once the program is running, please refer to this to work out what has changed to this program!

  #include "hmg.ch"

  FUNCTION Main()

  LOCAL cMainWinTitle := "ePortfolio"
  LOCAL cBtn1Cap := "Button 1"
  LOCAL nBtn1Row := 32
  LOCAL nBtn1Col := 64
  LOCAL nBtn1Wide := 60
  LOCAL nBtn1High := 100

  // Using a new type of variable! The following PUBLIC
  //   variable will be visible to ANY UDF and not
  //   just in FUNCTION Main()!
  PUBLIC lUsrResp := .F.

  DEFINE WINDOW Win_1 ;
    AT 0, 0 ;
    WIDTH 760 ;
    HEIGHT 640 ;
    TITLE cMainWinTitle ;
    WINDOWTYPE MAIN

    DEFINE BUTTON btn1
      ROW nBtn1Row
      COL nBtn1Col
      WIDTH nBtn1Wide
      HEIGHT nBtn1High
      CAPTION cBtn1Cap
      PICTURE "c:\hmg.3.4.0\MySourceCode\Icons\IconCh4.bmp"
      ACTION lUsrResp := UDFbnt1Clicked()
     // I don't need to pass any variables to this UDF
     //   because I have declared a PUBLIC variable which
     //   is visible to ANY other function after it is declared!
      END BUTTON

  END WINDOW

  CENTER WINDOW Win_1
  ACTIVATE WINDOW Win_1

  Return Nil

  /* -- This is a separator line -- */

  FUNCTION UDFbnt1Clicked()

  // No need to create any local variables and no need to
  //   pass a copy of any variable! LUsrResp is a PUBLIC
  //   variable so this UDF will be able to see it
  //   when the IF command executes and it can also change
  //   the value depending on what the user wants to do!

  IF lUsrResp
     lUsrResp := MsgYesNo("lUsrResp is TRUE. Keep it?", ;
      "Keep It")
    ELSE
     lUsrResp := MsgYesNo("lUsrResp is FALSE. Change it?", ;
      "Change It")
  ENDIF

  // I also don't need to pass the value back to FUNCTION Main()!
  //   lUsrResp is a PUBLIC variable so the value has already
  //   been changed above depending on what the user wants...
  RETURN Nil

You can download a copy of the source code file from here - HMGGuide-Ch7a.prg

The Dangers of Using PUBLIC Variables

OK, I bet you think that a PUBLIC variable is much easier to use in your code and you are right. However, there are very good reasons to use these with extreme care and, preferably, avoid them as much as possible. The 'good' thing about a PUBLIC variable is the fact that it is completely visible to any part of your program and that any part of your program can change it. The problems start if you happen to forget that a variable was declared as PUBLIC and you inadvertently create a new variable with exactly the same name in a different UDF. Things get even more complicated if you inadvertently change the type of value stored in a PUBLIC variable. To understand these 'unintended consequences' better, let's try two different experiments.

Firstly, create a LOCAL variable in FUNCTION UDFbnt1Clicked() and give it exactly the same name as the PUBLIC variable created in FUNCTION Main(), then give it a value of false. The code for UDFbnt1Clicked() will look like this;

  FUNCTION UDFbnt1Clicked()

  // No need to create any local variables and no need to
  //   pass a copy of any variable! LUsrResp is a PUBLIC
  //   variable so this UDF will be able to see it when
  //   the IF command executes and it can also change
  //   the value depending on what the user wants to do!

  LOCAL lUsrresp := .T.

  IF lUsrResp
     lUsrResp := MsgYesNo("lUsrResp is TRUE. Keep it?", ;
     "Keep It")
   ELSE
     lUsrResp := MsgYesNo("lUsrResp is FALSE. Change it?", ;
     "Change It")
  ENDIF

  // I also don't need to pass the value back to FUNCTION Main()!
  //   lUsrResp is a PUBLIC variable so the value has already
  //   been changed above depending on what the user wants...
  RETURN NIL

When you run the new version of the program, notice that the user cannot change the value of lUsrResp in the MsgYesNo() pop-up no matter which option is selected? This is because you have created a completely new LOCAL variable in UDFbnt1Clicked() which happens to have exactly the same name as the PUBLIC variable created in FUNCTION Main(). The compiler has the capability to handle this, so the program will work but it will not behave the way you expect it to! Now imagine it is several weeks after you originally wrote FUNCTION Main() when you may have forgotten you created a PUBLIC variable and imagine how difficult it is going to be to work out why the program is now not behaving as you intended it to.

As if that wasn't bad enough, let's see what happens if you accidentally gave this new LOCAL variable a different type of value such as text. In the code above, change LOCAL lUsrresp := .T. to LOCAL lUsrresp := "Hello" then recompile and try and run the program. Brace yourself because the program is going to crash when you click on the picture button!

Introducing Debugging

Oh, no.... your program has just suffered the dreaded runtime crash! The program compiled and ran fine until the user clicked on that picture button at which point they got the following program error. When they clicked the "Ok" button, the program closed completely and now they want you to fix the problem!

Runtime Crash

So, how do you go about fixing the problem? Firstly, let's hope the user took a few moments to write down some details of what they were doing before the crash happened and, hopefully, they even took a screen shot of the error to help you out as I have did above!

In this example, you need to look at the top line of the message which tells you Error BASE/1066 Argument error: conditional. An argument refers to a value contained in a variable that is being used by a command or function as part of its calculations. In this case, the command or function is expecting the argument it is looking at to be in a certain type of condition and found that it is not.

You now need to work out which command or function has encountered the problem so that you can try and fix it. This is shown above where it says Called from UDFBNT1CLICKED(55) which tells you that something is wrong with that UDF. To make things even easier, it even tells you which line number to look at, line 55 in this case.

When you open your source code and go to that line, you will see the code IF lUsrResp so you now know that the IF command has run into a problem trying to process the lUsrResp argument. Your next step is to investigate the value stored by that variable knowing that the IF command requires a logical condition (either true or false) for it to successfully execute. One quick look will tell you that lUsrResp has been declared as a LOCAL variable and given a value of "Hello" which is a text string, so it's type is text rather than logical!

You also will have noticed several lines below Called from UDFBNT1CLICKED(55) which have a similar use in troubleshooting and debugging a run-time error. The conditions that caused the crash are listed from top to bottom in reverse order that they were executed in by your program. For example, Called from (b)MAIN(31) refers to line 31 in your program source code (ACTION UDFbnt1Clicked()) which was the second last line of code that executed before the program crashed.

This section is an introduction to debugging so I am not going to go into any further detail at this time. I will cover debugging in greater detail later in the guide once you have developed greater knowledge and experience with the language. However, before we move on to the next chapter, there is one more thing to note. Modifying the code to make lUsrResp a logical value will fix the runtime error. However, it won't be long before the user informs you there is still a problem because they still cannot change the value of lUsrResp when they use the program! You already know that this is because you have both a PUBLIC and LOCAL variable called lUsrResp which you did as the first experiment earlier in this chapter and that this stopped your program from working as you expect it to do.

Despite everything I have said about the PUBLIC variable so far, you might still think there is a good time and place to use a variable that can be seen and changed anywhere in your program. You are right but there are safer, more elegant ways of doing this compared to using the PUBLIC variable. I will introduce a special type known as a STATIC variable and show you how to use them later in this guide. I will also introduce a very elegant and extremely powerful way of managing variables you may need to use in any part of your program that uses a different type of variable known as an ARRAY.

Chapter 6: Acting on User Responses <<     >> Chapter 8: ePortfolio Version 0.1