IBM®
Skip to main content
    Country/region select      Terms of use
 
 
   
     Home      Products      Services & solutions      Support & downloads      My account     

developerWorks  >  Lotus  >  Forums & community  >  Best Practice Makes Perfect

Best Practice Makes Perfect

A collaboration with Domino developers about how to do it and how to get it right in Domino

Users of other object-oriented languages may be accustomed to registering "listeners" (for the Java crowd) or "callbacks" (for C/C++). These are functions or classes that you can pass in to other functions or classes; when certain parts of the process are reached, your routine will be called. For an example of what I'm talking about, see the NotesTimer class; you use the On Event statement to register your own routine to be called when the timer triggers.

Problem is, there's no facility in LotusScript for creating your own event types, so you can't extend this system to your own classes. There's also not a way in LotusScript to get a handle to a subroutine, so you can't pass your subroutine to another subroutine as an argument.

What you can do, however, is define a class of your own with certain methods that the called code expects to find, and put your own code in those methods. Then, create an object of that class, and pass the object to something you're making a call to. When it gets to the appropriate point in its code, whatever you called invokes a method of the object you passed it (assuming that the method actually exists and takes the arguments it's expected to).

For a real-life example, consider the code I posted yesterday to sort fields in a table. This works great, except that it makes certain assumptions about how field names are formatted. It works in cases where the fieldnames in the table are of the form name?#, where ? is some dividing character, like underscore, and # is the row number starting with 1. Of course, Murphy's law says that when you have a restriction like that, the very first developer who tries to make use of it has an incompatible form -- they start numbering their rows at zero, or the row number doesn't come at the end, or (as occurred) both.

We could do little things to make this more flexible -- have them pass us a macro formula, or a LotusScript expression we would pass to Evaluate to calculate the field name, for instance -- but but that's a little slow, and not very powerful if you wanted to do more than just calculate a simple value. Worst of all, it doesn't demonstrate the technique I wanted to demonstrate.

So, with the new code (attached), if you want to use the table sorter, and you have fieldnames of a form other than name_#, you (the caller) have to supply a subroutine that calculates the name. You do this by creating a new class -- call it what you like -- with a method named Mangle, defined as follows:

Class ClassName
      Function Mangle(fname$, Byval row%) As String

             
' insert your custom code here
      End Function

End Class

For example, in the specific case, the developer has columns as follows: cat#, lot#, cst#, vb#_0, vb#_1, vb#_2, vb#_3, vb#_4,  vb#_5, vb#_6,  vb#_7,  vb#_8,  vb#_9, Desc#,  h#_0, h#_1, gp#, PercentIncrease#, pr#_0 where # is the zero-based row number.

So his callback class could be:

Class FNMSubZBase
      Function Mangle(fname$, Byval row%) As String

              Mangle = Replace(fname, "#", Cstr(row-1))

      End Function

End Class

and he could call the sorting function as follows:

Dim myMangle As New FNMSubZBase
Call SortATable(uidoc, "cat#, lot#, cst#, vb#_0, vb#_1, vb#_2, vb#_3, vb#_4,  vb#_5, vb#_6,  vb#_7,  vb#_8,  vb#_9, Desc#,  h#_0, h#_1, gp#, PercentIncrease#, pr#_0", "cat#", 10, myMangle, True)

At the receiving end, the myMangle argument is declared as Variant, because of course we have no idea what its actual datatype might be. Now, the code that does the sorting takes the myMangle argument, stores it, and when it needs to calculate the name of a field, calls its Mangle method:

For i = 1 To m_rows
      Set record = New sortingRecord(Ubound(m_keys), Ubound(m_fields))

      For k = 0 To Ubound(m_keys)

              fname = m_callback.Mangle(m_keys(k), i)

              record.keys(k) = doc.GetItemValue(fname)(0)
      Next

One problem with this approach is that if you mess up and pass in a class that doesn't have the right methods and arguments, you don't know about it until you run the code and it fails. Java handles this more gracefully, by letting you define an "interface" -- a class definition without any of the methods -- that you can use as a model for your callback class. The compiler confirms that your class has in fact implemented all the methods that are expected by the code you're calling. I can't think of any way to do this in LotusScript -- maybe one of you folks can.

Andre Guirard | 5 August 2008 10:30:00 PM ET | Home, Plymouth, MN, USA | Comments (6)


 Comments

1) Callbacks
Bill Buchan | 8/6/2008 3:54:55 AM

No, no explicit method of making the compiler understand his that I know of.

You could take it to the extent of actually passing code in a string, and using 'evaluate' to execute that code (if it were formula) or execute (if it were lotusscript).

Neither is recommended - I'd stick with the overriding-class model you outlined above.

---* Bill

2) OOLS: callbacks in LotusScript - that’s easy
Karsten Lehmann | 8/6/2008 7:34:44 AM

IBM should extend the Lotusscript compiler and editor. For example you could add code assist for user-defined Lotusscript classes like in the "modern" IDEs... Eclipse, you know ;-))

3) OOLS: callbacks in LotusScript
Brian Miller | 8/6/2008 9:53:08 AM

No, this is one of those places where we're really stuck.

I'd love to see functions as first-class objects in LS, but that just do happens to be one of the hardest things to program into any compiler/interpreter. At this point, I'm pretty sure that IBM would just rather use javascript for things, where it's already baked in, or Java, where you can do the interface and abstract method things.

4) callbacks in LotusScript
Matt Vargish | 8/6/2008 10:43:15 AM

I don't know of any foolproof way to get this at compile time either -- without support for interface, delegate, etc.

One thing you can to to provide a little bit of compile time type-safety is start with an unimplemented base class that you treat as though it is an interface:

Class FNMBase

Function Mangle(fname$, Byval row%) as string

Error 9999, "Mangle not implemented!"

End Function

End Class

Class FNMDefault as FNMBase ....

Changing your parameters to accept type FNMBase then allows them to receive subclasses of FNMBase which will guarantee at least that the compile time type is correct and the throwing a meaningful runtime error if the subclass does not override Mangle in the base class:

Sub New(kdoc, fields As String, keyfields As String, rows As Integer, _

callback AS FNMBASE, Byval ascending As Boolean)

Of course this isn't always a great solution if you want the "callback" class to inherit from another class, or if you have many methods that need to be exposed (maintained) in the base class.

On Bill's note about evaluate, if you really needed runtime class selection, you could combine this technique with the that outlined in "Dynamic Script Library Loading" in the old Performance Considerations redbook (modify the factory method "produce" to take a parameter of the base class type which will receive the new instance).

As an fun side note, using inheritance in these classes could really let you confuse the heck out of junior developers:

Class FNMExceptRowOne as FNMDefault

m_baseMangle As FNMBase

Sub New(baseMangle as FNMBase)

Set m_baseMangle = baseMangle

End Sub

Function Mangle(fname$, Byval row%) As String

If row = 1 Then

Mangle = fname

Else

If m_baseMangle is Nothing then

FNMDefault..Mangle(fname, row)

Else

m_baseMangle.Mangle(fname, row)

End If

End If

End Function

End Class

5) OOLS: callbacks in LotusScript
Andre Guirard | 8/6/2008 12:15:14 PM

I agree with Matt. It can make sense to define the argument as being of a base class so that the compiler can catch some problems. You might even want to have default implementations for some of the base class methods (instead of throwing an error). After all, the caller might not want to handle all the events, so "do nothing" is a sensible default implementation in many cases.

It does, though, decrease your flexibility to use a declared base class, since you can only inherit from one class. If you're doing object oriented programming anyway, it's great if you can use a method of the calling object as your callback rather than having to define a whole new class. Then, the caller can just pass this as the callback argument.

6) OOLS: callbacks in LotusScript
Lars Berntrop-Bos | 9/1/2008 8:22:10 AM

I remeber Alain Romedenne posting a reply to a post by Bill Buchan stating he has a method for doing interface and implementation style programming using dynamically loaded classes.

<searching...>

Here's a post by Alain describing the mechanism: { Link }

 Add a Comment
Subject:
   
Name:
Comment:  (No HTML - Links will be converted if prefixed http://)
 
Remember Me?     Cancel

Search this blog 

Disclaimer 

    About IBM Privacy Contact