If your function is somewhat complicated, try writing a script to do the same thing. Scripts are a lot easier and quicker to debug. (I often just insert a Show Custom Dialog at interesting points to see what's going on.) Once the script runs exactly as you like, converting to a custom function is usually pretty easy.
Or sometimes I just change the custom function (temporarily) to return some intermediate result, which often helps me spot errors very quickly. Once I fix the bad code, I change the function back so it returns the intended result.
Ian may want a custom fit,
My custom functions all use the Let() function. I copy the CF to the Data Viewer and then add data entry definitions in front of the 'normal' variable definitions.
For example in this CF I'd add extra definitions for field1 and field2 before the normal CF definitions. This can be two real fields or just sample data.
CF = comparefield ( field1 ; field2 )
comparefield ( field1 ; field2 )
Compare two fields from related tables
The field name in variables "field1" and "field2" must include the relationship name
Must have a relationship between tables.
Let ( [
//added definitions for field1 and field2 here when debugging, not part of CF
field1 = company::company_name ;
field2 = customer::company_name ;
source1 = TextColor ( GetField ( field1 ) ; RGB ( 0 ; 150 ; 0 ) ) ;
source2 = TextColor ( GetField ( field2 ) ; RGB ( 150 ; 0 ; 0 ) )
source1 = source2 ;
TextStyleAdd ( Right ( field1 ; Length ( field1 ) - Position ( field1 ; "::" ; 1 ;1 ) -1 ) ; DoubleUnderline ) &
source1 & TextColorRemove ( " - has changed to - " ) & source2
Note that I added the field1 and field2 definitions to the CF but in reality they would not be there.
Generally speaking if it's a complex custom function, I build it in stages (using Let()), testing each stage in the data viewer, then assemble. The result is that my functions look like this:
v1 = something1;
v2 = something2;
v3 = something3
The other technique I use for troubleshooting is to set intermediate values into global variables using Let(), then evaluate your function in the data viewer. The global variables will be created and persist with the values assigned to them as the function executed. This is a good way to follow the logic of your function, but only works where your function isn't recursive of course! Unfortunately I have the type of mind that tends to create recursive function solutions, so I don't use this technique as much.
I was wondering if any one has other tips / techniques on how to debug custom functions
Assemble large functions from components which are known to work. You may even discover that you'll write a lot of small functions that are then called to perform the more complex function.
Are you talking about a recursive custom function or a single iteration function? I don't really have trouble with either in the data viewer, but the since recursion only happens in the CF engine, you can't see the complete result of recursive functions until you load them as custom functions (and some of the calculation you write just can't be done in the data viewer).
That said, I have a pretty rigid format for working in the data viewer that makes it pretty easy to get intermediate results and debug individual elements. Here's a simple conversion from a timestamp that FMPro doesn't read properly into one that it does:
text= "2012-03-11 7:45 PM EDT" // or I can use a field reference here
/* Get the info for the Date */
; year = Middle ( text ; 1 ; 4 )
; month= Middle ( text ; 6 ; 2 )
; day= Middle ( text ; 9 ; 2 )
/* Get the info for the Time */
; hours= Middle ( text ; 12 ; 2 )
; minutes= Middle ( text ; 15 ; 2 )
; seconds= Middle ( text ; 18 ; 2 )
; result= Timestamp ( Date ( month ; day ; year ) ; Time ( hours ; minutes ; seconds ) )
) // end Let
I don't have to break things down into such small 'atoms', but it certainly makes it easier to understand once I've forgotten how I got a particular result.
Putting the semicolons at the beginning of every new line means that I can easily // comment out something that isn't working.
Putting every part of the calculation before the ]; means that I can easily swap out the final 'result' for any of the intermediate values.
You can actually have multiple copies of a calculation variable throughout a calculation e.g.:
; return= Char (13)
; result= something
; result= Sustitute (something ; return ; "," )
) // end Let
Hope this Helps,
This is a great tip.
Takes more time, but i definately see the benefit here..
This is what i have been doing as well.
I am talking about recursive , Iterative and normal. Even tail recursion.
What i am noticing is that CF's need to eliminate certain criteria in order to continue instead of wasting time; i guess
that means one has to draw the idea out first. Which i tend not to do and end up wasting loads of time.
by: * Agnès Barouh - Janvier 2008
Let ( [
Portal = PortalName ; Object = ObjectName ;
LineHeight = GetAsNumber ( GetLayoutObjectAttribute ( Object ; "Top" ; 1 ; 2 ) - GetLayoutObjectAttribute ( Object ; "Top" ; 1 ; 1 ) ) ;
Tolerance = Case ( IsEmpty ( TolerancePixel ) ; GetAsNumber ( Ceiling ( ( LineHeight / 4 ) / 2 ) ) ; TolerancePixel )
Tolerance + 1 ≥ 1 and Tolerance ≤ ( LineHeight - 3 ) and
ValueCount ( FilterValues ( LayoutObjectNames ( Get ( FileName ) ; Get ( LayoutName ) ) ; Portal & ¶ & Object ) ) ≥ 2 ;
Let ( [
Agnés has declarations in 1st Let statement ; then she does a check with Case to make sure that we have at least 2 values ( a portal & an object) as well as checking for tolerance . then she goes on to get to the nitty gritty of the function.
Recursion: when to use
Tail Recursion: when to use ( filemaker engine has limit)
Those two i am still trying to understand fully ...Even if they are necessary at times?