Version: 1.00.00 - last update: Sunday, September 7, 2014 [created]
Is it possible to really return a 'NOTHING' from a method?
Intro
FoxPro makes coding easy for us! We are allowed to name our methods Procedures or Functions interchangeably. The Fox don't care if we return a value from one of our Procedures, or if we omit it completely! The Fox gracefully adds his own Return .T. and, voilà, nothing can go wrong. What Lucky guys we are!
Some Short Mantras
The following "mantras" should remind you of some basics of almost any (non-functional) programming language.
A Function is a Function is a Function
A class method that acts as a function should be declared so (if PRG-based in VFP).
Function MyFunc(tcParameter As String) As Boolean
EndFunc
A Function must return at least on value. One should always code the return statement at the end of the Function, to remember novice VFP programmers of that fact!
Function MyFunc(tcParameter As String) As Boolean Return .T. EndFunc
-
A Function must not return more than one value (which can be an array in VFP 9, too)!
-
A Function can signal internal malfunctions, or parameter errors (broken contracts), by returning an appropriate error value, or by raising an error!
-
A Function must never alter its object's state.
-
A Function obeying to the rules above can be said to be side effects free.
A Procedure is a Procedure is a Procedure
A method that acts as a Procedure must be declared so, if PRG-based.
Procedure MyProc(tcParameter As String) As Void EndProc
-
A Procedure must never return any value. Thus, its return type is called VOID!
-
A Procedure is allowed to alter its object's state.
-
A Procedure can signal any internal malfunction, or parameter errors (broken contracts), only by raising an error!
-
A Procedure obeying to the rules above can be said to be well behaved.
What's Not Good About It?
There's nothing really wrong! If only FoxPro would not return a Boolean .T. value from methods we named Procedures in our code!
Instead of this odd behavior, wouldn't it be much cooler if FoxPro could really return nothing from a Procedure? That would have the consequence that any assignment of the return value of a procedure would raise an error, because one cannot add nothing to a variable.
Why Would We Like to Have That?
Maybe novice VFP programmers get tricked by the existence of the return value, wondering what the <.T.> return value is all about?
My personal favorite is that it always brings back my focus to the elemental rules described above! When I come to the point during coding, thinking about to return some value from a method I defined to be a Procedure before, then I'm instantly warned! It always seems to be a good thing to take a short break, or at least a deep breath, and then, start refactoring the formerly procedure-based concept!
Another real existing problem could also be solved. What if you need an additional value type? At the moment we have "U" for undefined, "X" for .Null. and a list of uppercase letters for all other real existing types. What, if we could make a property holding another virtual type of value, namely .Void. returning an empty type-letter <" "> when queried using VFP's Vartype() and Type() functions?
A VOIDED value (.VOID.) is an abstract concept denoting some
|
How to Engage The Concept?
The integration into our existing code base is very simple and looks like this:
Procedure MyProc(tcParameter As String) As Void Return This.Void EndProc
The above code overrides the default return value of <.T.> of the MyProc() Procedure. What gets returned now is the content of an property called .void.
Now, we only have to add the above Return statement to the end of our procedures, that's all it takes!
What Happens if We Touch The Void?
Not much, unless we try to assign a voided value to another variable/property, or try to use it in calculations. Here come the DOs and DONTs:
DOs
We can query a property, or the return value using Vartype() and Type() like so:
VARTYPE(m.goReferenz.MyProc())
TYPE("m.goReferenz.MyProc()")
Which will give you a .Void. return value, a value of without type and content!
DONTs
We cannot assign the property, or return value, to another variable like so:
loRetVal = m.goReferenz.MyProc()
which would throw an exception immediately!
What Gets Stored in The Void Property Actually?
Well, to be exact, in VFP .VOID. is a value represented by a corrupted value structure (structure with empty 'ev_type' member). To understand the last sentence we have to know how VFP handles values internally (using C-structures). The elements of the structure describing the value handled by the structure. Short data-types are stored directly within the structure, strings (stored somewhere else in the heap) are pointed to by a special member of the structure. There is more about the VFP C-API basics in VFP's dv_foxhelp.chm file. Try the following URL into your Explorer:
mk:@MSITStore:C:\Program%20Files\Microsoft%20Visual%20FoxPro%209\dv_foxhelp.chm::/html/ad9896fa-643e-48d3-a3b9-d738971e71a3.htm
the link above points to one of the API-chapters called "Parameters in External Libraries".
How to Get That Damage Done?
This was cumbersome to find out! I started some years ago with a little test form like shown in the screenshot below. Note, that there will be NO download for the SCX, shown below, because you can write it on your own after having read all this, and if that is still interesting for you afterwards.
I found VFP's value checking flaw incidentally while experimenting with some specific VFP behavior. At that time, I was chasing for detailed .WriteExpression() and .ReadExpression() method behavior, when I incidentally called the Object.WriteExpression() method passing in an empty value, I encountered errors while trying to access the property subsequently!
I created the above demo form only to preserve my findings. After I had finished my exhaustive observations of VFP's .WriteExpression(), I came to some very interesting findings, I kept secret - until now…
Something to Suit my Needs
When I needed a new return value attribute in my framework, I called it contract broken. I restarted thinking about the possibilities how to signal the fact back to a caller, that she just broke the signature contract protecting the current message.
One part of any Function Contract covers the return value of the function. A warranted type, and a set of valid values is described in the contract there, as well as the values to be returned if an error occurred!
What could we do in cases where the returned value is of type Variant – and there are no restrictions on the returned value, too? A returned .Null. value could be valid just as well as any other kind of date. What's still missing is a memory-based equivalent of a BLANK field value in VFP.
Okay, you might argue, in our case it's time to raise an exception. Right you are, I must admit, this is a proven way out of there. But…
…signaling any "soft-error" stemming from a broken contract raising a VFP exception definitely is to much (IMHO)! User defined errors should be traded on another message transportation level. Exceptions are reserved for what they are intended to be – for all situations at runtime, where the system runs into a not predicted, invalid state (for whatever un-anticipated reasons)!
Thus, I designed my framework to never except any other 'return value' from a procedure (method) but the '.Void.' one. Soft-errors are reported back through an IN/OUT parameter that is passed in through a parameter object.
How to Apply The Void?
We could implement a protection scheme for our PEMs. If a caller isn't authorized to query our instance we could simply return our void property value and watch the "Kaboooom!" :-)
We also are able to change the voided property value from the inside. This could become handy in some internal programming scenarios: Chaining of procedure calls can be written more easily, if the involved procedures return some Boolean value all together, like so:
*\\_____________________________________ *// Work the ToDoList Do Case Case Not This.CheckPermission() This.Log([CheckPermissionError]) Case Not This.LoadConfig() This.Log([LoadConfigError]) Case Not This.BuildObject() This.Log([BuildObjectError]) Case Not This.StartObject() This.Log([StartObjectError]) Otherwise *\\_______________________________ *// Start main app
This.StartEngine() This.ShowGui() This.ReadEvents() This.HouseKeeping() EndCase
One nice thing is that we can change the real value of our .void. property from the inside on the fly! We are able to store any other 'unbroken' values there temporarily. Thus, to run the last example code above, we would simply store <.T.> to our This.Void property before running the Do Case chain above. Afterwards, we will reset our property back to its voided value.
To make our code bullet-prove, we have to check all occurrences of VFP's Vartype() and Type() functions in our code, and try to evaluate, if a .void. scenario might apply in one or more of our contexts!
The following code fragment shows us how to to test a property at runtime fail-safe:
If Empty(Vartype(m.loInstance.void) DebugOut "Class instance is Void!" Endif
We cannot test the return value of a method without running its behavior. Thus there is not much we can do to trap for a returned void value. Thus, there should be an appropriate documentation, or even better, an Intellisense-based guidance available, telling us what methods are functions and what other methods are procedures that will return THE .VOID.!
Some Evil Pitfall
It may happen to you, that this trick doesn't work: You cannot call you object's WriteExpression() without throwing an exception!
It took me some time spent on a lot of investigative testing to find out what causes this misbehavior to appear. This is not a trivial one, because it forces us to adapt out overall implementation strategy…
Stay tuned!
<to be continued…>
No comments:
Post a Comment