Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Marco Plaza's Code for nf_num2en.prg

Status
Not open for further replies.

ggreen61

Programmer
Jun 19, 2013
187
2
18
US
Marco--

First, thank you for sharing your code. In the code, you declare the arrays as private:

Code:
private abn,aun,atn,atw,an

I am curious as to why? I have gotten away from using private variables since the scope of them is visible to called programs which in the past has caused me many issues before the LOCAL declaration came about. I have a check in my editor to show undeclared variables (which by default would be PRIVATE). For arrays, I use the following declaration:

Code:
LOCAL ARRAY abn[1], aun[1], atn[1], atw[1], an[1]

This initializes and declares the array as a local object. If I have to pass an array to a function, then I pass by reference, @abn; or I use an object that has an array assigned as a property.

But I am just curious why you use PRIVATE?

Thank you.

Greg



Greg
 
worth reposting the code here as i suspect that other thread will get removed soon....

Code:
*--------------------------------
* Marco Plaza, 05/2022
* nf_num2en.prg @nfox.dev
* ( use it as you like )
*--------------------------------
lparameters num

local num,cnum,rn,n1,n2,n3,x
private abn,aun,atn,atw,an

cnum = transform(m.num,'999999999999999.99')

alines(abn,"trillion,billion,million,thousand,,",2,',')
alines(aun,"one,two,three,four,five,six,seven,eight,nine,ten",1,',')
alines(atn,"ten,eleven,twelve,thirteen,fourteen,fifteen,sixteen,seventeen,eighteen,nineteen",1,',')
alines(atw,"twenty-,thirty-,forty-,fifty-,sixty-,seventy-,eighty-,ninety-",1,',')

rn = ''

for x = 1 to 18 step 3

	alines(an,transform(substr(m.cnum,m.x,3),'@R 9,9,9'),2,',')
	n1 = val(an(1))
	n2 = val(an(2))
	n3 = val(an(3))

	rn = m.rn + iif( m.n1=0,'',' '+aun(m.n1)+' hundred')
	rn = m.rn + iif( an(1)='.' and m.n2+m.n3 > 0,'and','')
	rn = m.rn + iif( m.n2=0,'',' '+iif(m.n2=1,atn(m.n3+1),atw(m.n2-1)))
	rn = m.rn + iif( m.n3=0 or m.n2=1,'',' '+aun(m.n3))

	rn = m.rn + iif(m.n1+m.n2+m.n3>0,' '+ abn((m.x-1)/3+1),'')
	

endfor

rn = strtran(m.rn,'- ','-') + iif(' and '$m.rn,'hundredths','')

return ltrim(rn)
 
PRIVATE is not a declaration, it just hides already existing private variables of that name until the code ends. The names therefore become avaiable from that line onwards to use them for own private vars. Which is likely the reason people often read it as declaration.

The PRIVATE command protects variables in private scope defined outside of the function by "parking" them, so there is no side effect of anybody using private variables abn,aun,atn,atw,an for whatever other use case before calling nf_num2eng.prg. After the function ends it's own private arrays are released and the hidden private variables become visible again and are untouched.

If Marco wanted to use local arrays, that's possible, too, as ALINES will take any already existing array and extend it. But not by simply replacing PRIVATE with LOCAL.

Code:
Local abn
alines(abn,"trillion,billion,million,thousand,,",2,',')

Causes error "abn is not an array". The replacement with local variables would mean to use LOCAL abn[1],aun[1],atn[1],atw[1],an[1] to define these vars as arrays and let them be dimensioned by ALINES. Or dimension them to the necessary size in the LOCAL statement. A function not making further calls can use private variables just like locals, a possible disruption comes, as you say, Greg, when you do call further programs and they meddle with them.

LOCAL is good practice for something used local, that's true, but there is no big benefit about LOCAL nor a risk in using PRIVATE. Especially since a same name private variable defined outside doesn't penetrate this function. PRIVATE ALL would also work.

Chriss
 
Chris--

The risk in PRIVATE vs LOCAL is demonstrated below (note that if you do not 'declare' a variable, then by default it is PRIVATE):

Code:
PRIVATE pnValue
pnValue = 1
?pnValue  && Displays 1
CallUDF()

?pnValue  && Displays 2

FUNCTION CallUDF
   pnValue = 2
ENDFUNC

Since the variable is declared as PRIVATE in the top procedure, the CallUDF() is changing the same variable as the parent procedure. PRIVATE allows the variable to be in-scope for all child methods/procedures. Yes, each declaration causes a 'new' variable to be created, but if you missed declaring it as PRIVATE or LOCAL then you can have unexpected variable changes. This behavior is what I don't like. If I want a called procedure to access the calling procedure's variables, then I pass them as parameters. If I want the value returned, then I use the RETURN statement to pass the value back (if multiple values are needed to be passed back then I typically use a custom object with properties) or I explicitly pass them by reference. The same code with the variable defined as LOCAL:

Code:
LOCAL lnValue
lnValue = 1
?lnValue  && Displays 1
CallUDF()

?lnValue  && Still Displays 1

FUNCTION CallUDF
   lnValue = 2   && by default this is a new variable with PRIVATE scope
ENDFUNC

Note that I use the prefix "l" for LOCAL and "p" for PRIVATE. Since the second example declares the variable as LOCAL, the value is not changed by the call to the CallUDF() function.

Greg
 
Hi Greg,

As Chris already stated, in this context it makes no difference...

The thing is I have a little helper that do the locals and I have it configured
that way for a particular project.

This function is a "blind-street".. so don't worry.

Also, Chris asked for a 'better naming' and here it is with a little improvement here and there:

Ps:
I would like to highlight the fact that I see no real need for "refactoring" the good-old working code from ramani;
just made this for fun of programming.. rethink a routine that we all had to do many years ago when checks where in use.


Code:
*--------------------------------
* Marco Plaza, @nfoxdev, 2022
* nf_num2en.prg v 1.1
* use as you like
*--------------------------------
lparameters numInput as number,ldecAsNumber as boolean as string

if not vartype(m.numInput) $ 'NY' or vartype(m.ldecasnumber) # 'L'
	error 'invalid parameter type'
	return .null.
endif

local maskedInput,num2en,numAtPos1,numAtPos2,numAtPos3,groupStartPos,groupn,rem
private aBigNum,aUnits,aTenths,aTwent,charAtPos

maskedInput = transform(m.numInput,'999999999999999.99') 

alines(aBigNum	,"trillion,billion,million,thousand,,",2,',')
alines(aUnits	,"one,two,three,four,five,six,seven,eight,nine,ten",1,',')
alines(aTenths	,"ten,eleven,twelve,thirteen,fourteen,fifteen,sixteen,seventeen,eighteen,nineteen",1,',')
alines(aTwent	,"twenty,thirty,forty,fifty,sixty,seventy,eighty,ninety",1,',')

num2en = ''
groupn = 0

for groupStartPos = 1 to iif(m.ldecAsNumber,15,18) step 3 

	alines(charAtPos,transform(substr(m.maskedInput,m.groupStartPos,3),'@R 9,9,9'),2,',')
	
	numAtPos1 = val(charAtPos(1))
	numAtPos2 = val(charAtPos(2))
	numAtPos3 = val(charAtPos(3))

	num2en = m.num2en + iif( m.numAtPos1  = 0 ,'',' '+aUnits(m.numAtPos1)+' hundred' )
	num2en = m.num2en + iif( charAtPos(1) = '.' and m.numAtPos2+m.numAtPos3 > 0 ,'and'	,'')
	num2en = m.num2en + iif( m.numAtPos2  = 0 ,'',' '+iif(m.numAtPos2=1,aTenths(m.numAtPos3+1),aTwent(m.numAtPos2-1)+'-') )
	num2en = m.num2en + iif( m.numAtPos3  = 0 or m.numAtPos2 = 1 ,'',' '+aUnits(m.numAtPos3) )

	m.groupn = m.groupn+ 1
	num2en = m.num2en + iif(m.numAtPos1+m.numAtPos2+m.numAtPos3>0,' '+ aBigNum(m.groupn),'')

endfor

* remove space after twenty etc..
num2en = strtran(m.num2en,'- ','-')

* reminder
rem = mod(m.numInput,1)*100

if m.rem>0 or m.ldecAsnumber
	num2en = m.num2en + iif(!m.ldecasNumber,'hundredths','and '+transform(m.rem,'@l 99')+'/100')
endif

return ltrim(m.num2en)

*---------------------------------
procedure testme
*---------------------------------
clear
okres = 'nine trillion one hundred fifty-one billion one hundred eleven million two hundred fifty-four thousand one hundred fifty-five and nineteen hundredths'
cures = nf_num2en( 9151111254155.19 )
? m.okres
? m.cures
? 'test ok?',m.okres = m.cures



Marco Plaza
@nfoxProject
 
Marco--

I realized from the start that it made no difference in the context of this program. Either way your program will work with no problems since it does not call any other UDF. Actually, the line:

Code:
private aBigNum,aUnits,aTenths,aTwent,charAtPos

could be eliminated; these array variables will be PRIVATE by default when created by the ALINES() command (again, no problems having it). In my coding practice for VFP I have adopted to never use PRIVATE and always declare all variables as LOCAL. Globals, if needed are added as properties to _VFP system variable. In the past, I have had problems with PRIVATE declared variables or variables that were never declared getting unexpectedly changed and the debugging to find the culprit can be time consuming. I have the ability to highlight any variable that is in a method or UDF that has not been declared to be LOCAL or LPARAMETER; this is my attempt to eliminate the scope "side-effects" for PRIVATE declared variables.

I was just curious as to why you specifically chose PRIVATE rather than LOCAL ARRAY...

Greg

Greg
 
well.. no .. to isolate from caller programs
private can't be eliminated.

( Check again Chris post on that regard )

try:
Code:
dimension ss(10)
ss = null
display memory like ss
alines(ss,'a,b',1,',')
display memory like ss


Marco Plaza
@nfoxProject
 
Greg,

you miss the point. Your code demonstrating the bad side effect comes from the bad practice to not use PRIVATE within the CallUDF function to distinguish its own private variable use from outside private variables.

In your last post you show again how you miss the point that indeed only a line like PRIVATE listing vars you want to be sure will be generated new by your code will prevent you to change other peoples private vars. Marco could have made that easier with PRIVATE ALL, but indeed most code not using a private command is just sure of itself only using its own local vars. It's a small danger indeed, not for yourself, for the caller. And therefore calling code usually does not use private vars, and they vanish from being used.

Private vars only unfold their potential to save time for declaration and parameter passing when you create them in one PRG/procedure/function/method/event and then call something that explicitly knows about them and is tailored to use them in whatever way necessary. Also to change them can be a wanted effect instead of unwanted side effect.

We are able to do without private variables in any other language than VFP, it's a concept that proves itself as rarely useful and creating a lot of misunderstandings. If you want your variables owned by only you, you declare them LOCAL, if you want to share them with what you call (and also what that code calls, etc.) you can pass them by reference. In VFP you can also make them PRIVATE and will therefore not need to pass them on at all, they stay in scope.



Chriss
 
Marco--

Sorry, but I do not understand what you are trying to explain. Let me try with an example code to show what I am doing:

Code:
DIMENSION laArray[2]   && By default this is a PRIVATE scope
laArray = 1
? laArray[1]
? laArray[2]

MyUDF()

? laArray[1]
? laArray[2]

FUNCTION MyUDF
laArray = 2
ENDFUNC

The above will output to the screen 1, 1, 2, 2 for the array values. What I want is MyUDF not to be able to change the values of the array but to see the variable name as another variable. This is achieved with the following:

Code:
LOCAL ARRAY laArray[2]
laArray = 1
? laArray[1]
? laArray[2]

MyUDF()

? laArray[1]
? laArray[2]

FUNCTION MyUDF
laArray = 2
ENDFUNC

The above will now output to the screen 1, 1, 1, 1 for the array values; the array is not changed by MyUDF function. The variable is a new variable and not scoped from the calling program. However, if I want MyUDF to change the values, then I would pass by reference as follows:

Code:
LOCAL ARRAY laArray[2]
laArray = 1
? laArray[1]
? laArray[2]

MyUDF(@laArray)

? laArray[1]
? laArray[2]

FUNCTION MyUDF
LPARAMETERS taArray
EXTERNAL ARRAY taArray
taArray = 2
ENDFUNC

Hope the above will clarify what I am trying to ask in regards to PRIVATE vs. LOCAL.

Greg



Greg
 
Greg,

Greg said:
I want MyUDF to change the values, then I would pass by reference

Indeed that concept makes private vars obsolete, also within VFP, but if you want, you can do without any parameters and any passing on of values when using the PRIVATE scope. It's a strange scope, as it's not bound to a class or a PRG, anything that is called has access to the private variables, also code not intended for it.

Chriss
 
You can do something like this to have encapsulation in procedural programming. In this scenario a private variable from the main code of a PRG is like the property of a class. It is available to the main PRG code and the internal functions within the same PRG used by it or using each other:

Code:
Private pScopedLikeaPRGProperty
pScopedLikeaPRGProperty = 42 && initializing = creating the actual variable
prg_internal_function()

Function prg_internal_function()   
   If Not Program(Program(-1)-1)==JustStem(Sys(16))
      Error 'prg_internal_function is only for internal use of '+JustStem(Sys(16))
   ENDIF
   * pScopedLikeaPRGProperty is accessible here and is defined from the main code in this PRG, not coming from elsewhere
   * because the only entrance to here is calling the PRG.
   * The PRG main code hides any previously defined private variable of the same name and generates a new one.

You can now technically still SET PROCEDURE TO the PRG with this code, but when you try to call prg_internal_function() the function will error. The function is private to the PRG just like the private var.

Chriss
 
the following assumption is not true.. ( that's the problem )

ggreen61 said:
DIMENSION laArray[2] && By default this is a PRIVATE scope

I failed on my first sample... so:
Code:
clear
dimension names(2) 
names = 'my x value'
display memory like names
 num2letters()
display memory like names 
? "names() was changed by num2letters()!"


dimension names(2) 
names = 'my x value 2'
display memory like names
 num2letters2()
display memory like names 
? "names() was'nt changed by num2letters"


*----------------------
function num2letters()
*----------------------

dimension names(10)
alines(names,'a,b',1,',')


function num2letters2()

private names && hide existing names  
dimension names(10)
alines(names,'a,b',1,',')




Marco Plaza
@nfoxProject
 
Marco--

The second function, num2letters2, forces a new variable to be declared with the same name as the parent with the private declaration. VFP is 'smart' enough to create a new variable and not use the same one from the calling program in num2letters2. I just don't like the potential to change a variable's value in a called program that is declared in the parent unless it is passed by reference -- passing by reference is a clear indication in the program code that the value can be changed. I have found that if I have to come back to the program and make changes maybe years later, that it is harder to make changes if the variables can be changed in any of the 'child' functions by use of the private scope.

I was just wondering on your use of private when you also used local. Why not both private or both local? Why the choice of using both? I just wanted to learn if there is a particular reason for doing so.
 
Greg,
The above sample was just to show the behaviour of the private declaration.

Also, all that you say about passing arrays by reference it's totally right.





Marco Plaza
@nfoxProject
 
Greg,

finally we can say that you can do totally without private vars by means of other techniques like passing by ref and for an extended scope between local and global, I'd rather use a property of a class. But the way legacy screen work(ed) with READ states allowed to use private vars within them as quasi properties, that use is overcome with OOP and so finally, yes, doing without them is not only possible but totally okay. No reason to think there is still some use case you're missing, even saving time for passing on is very marginal. But also no reason in this context to think there's harm possible. The function itself harms no developer using private vars as it explicitly doesn't touch them. So the only risk is sometimes in the future calling something into which the private arrays used bleed in and that modify them.

I think it's still important to know about them as they are a feature you can't turn or configure off and private vars can happen anytime you have a typo in a name differing from the LOCAL declaration. It should show, if it's the only place you make the typo, when you eventually then have two vars with similar but not the same name, one local and one private. It can slip tests not covering all branches of the code and so not covering all corner cases in unit tests. That makes PRIVATE ALL a good idea, for example, in all code. But who wants that?

You can insist all your code has no typos and errors and therefore is hardened against bleeding in private variables as well as having no own private variables bleeding into to code called by you, but it can still happen that you accidentally create or modify a private variable. Tools like Thor offers for that matter (adding LOCALs for variable names used in the code) can help with it. It's still not causing compilation errors, if something slips through, so no reason to be sure something like it won't ever happen as you don't actively and intentionally use private vars.

In any case, even with tool help you will need to check what the variable declaration and use fits your expectation. It doesn't help that Thor catches you having liCount and lnCount in code, for example, if some code address the one and other code addresses the other name and the overall result fails to work because it actually has two count variables and would only work if all code uses the same variable. That's not arguing for private vars, but for not relying too much on being protected against them by not intentionally using them.

Chriss
 
Chris--

I recognized from the start the particular use of PRIVATE in Marco's code example did not have a problem -- there were not any subsequent calls to other UDFs. I just wanted to know why he chose to use it when he obviously used LOCAL in declaring variables right before the line with PRIVATE. Why not LOCAL for both? Was there a particular reason for use of PRIVATE? I was interested in learning if there were some other benefit that I was not aware of.

I would have preferred that PRIVATE would have been eliminated from VFP and the forcing of declaring each variable and type (like other languages). Having a variable being able to be defined simply by mistyping it leads to errors/bugs that a compiler should be able to catch right away. But alas, that is not the case and now I must try to overcome the 'agony' of private when a variable is mistyped. Also, I am not sure of the effect of declaring variables LOCAL and then having the PRIVATE ALL declaration. Does the private all override the local? If the local is declared after the PRIVATE ALL, does it override? I could build my own tests to determine the outcomes, but I just use LOCAL. The editor that I use for VFP (forms, visual classes, programs) has a high-light capability to show me undeclared LOCAL variables as shown (the yellow backgrounded variable):

2022-05-05_12-55-00_obxrzy.jpg


I use this capability to verify all variables are declared as LOCAL. The editor also has the capability to high-light all the occurrences of a selected variable (much like NotePad++). Between these two capabilities I am fairly certain I am not missing a variable in a typo.

Greg

Greg
 
Hi Greg,

PRIVATE ALL doesn't override local, as it's not a declaration command. The moment PRIVATE is called it take any already existing private vars with the given names or name patterns (PRIVATE ALL LIKE/ ALL EXCEPT)and hides them - whish is shown as variable scope (hid) in LIST MEMROY. They will be unhidden at the time vars are released at the end of the current method/event/procedure/function/PRG. That should be clear by now.

Nice editor feature, Thor's Create LOCALs would simply create LOCAL lcUndeclaredVar. If you're using the tool you'll find your typos in one of the LOCAL declarations, but only if you look closely. The risk then isn't having a private var, but two LOCAL vars that should actually be the same, which can also cause problems. In the end there's no Lint tool that'll recognize you actually meant the same variable name, which makes ZLOC valuable. Do macros like ZLOC work in your editor?



Chriss
 
Chriss--

The editor is not as directly extensible as Thor was designed. It is based on VFP code and several ActiveX components. So, it is extensible from the point of view of having the source code. Something like Thor's interface could be developed; I just never did. Back when Thor was first being developed by Jim Nelson, he and I talked about an attempt to integrate. Unfortunately, we did not see a way since My editor does not interface back into the VFP development environment the same as Thor does. The code editor component has its own commands for accessing the code lines and tokens which is not easily exposed to something like Thor. I do have a 'hot-key' for a selected variable to auto-add it to the LOCAL command line at the beginning of the procedure/function. Most of the Thor functions that Jim was adding was also added into my editor as well. The editor also has its own macro recording facility for saving repetitive key-strokes and then calling via hot-keys.


Greg
 
I have not used private or public variables in over 25 years - only local. The only public variable is goApp - the application object of our framework. In large applications, private and public vars can be very dangerous and difficult when debugging. Encapsulation is very important and comes so natural now that I don't even think about it.

However, there are cases where variables need to persist for the entire session of the application. Our application object is a collection containing five additional collection object managers (colCursorMngr, colFormMngr, colTempFileMngr, colWinsockMngr and colGlobalObjectMngr). The colGlobalObjectMngr is used to add, change, and persist variables as properties of "Empty" objects added to the colGlobalObjectMngr at design and runtime. While it is true that the Set|Get calls require a bit more code, encapsulation is honored.

 
I fully agree with Mike here. While we can discuss the nuances of public and private variables, the best solution | best practice calls for encapsulation which makes public and private moot.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top