Sunday, June 01, 2008

LotusScript's List bug strikes again

Last week i was working with a LotusScript agent which uses List data type for storing text values. The agent was not working properly as it could not find matching values in the second list(which was populated with the same listtags as the first list). I suspected it was something wrong with processing of List, but couldn't find what part of the code was wrong, as I got no error messages. The List was more than 1000 elements and in the debugger I could only see about 200 first elements, which was not very helpful. I have earlier experienced similar problem with IsNull(mylist("listtag")) function, but in this agent there were no such IsNull checks (use IsElement instead of IsNull). After 2 hours of commenting parts of the code out, I finally found what was wrong. The fact of calling tst procedure with nonexistant list value mylist("b") as parameter, creates "b" list element with value ""! It does not generate "List item does not exist" error message as one would expect!

I have hard to think that it works "as designed", as a programmer clearly doesn't want to create a new list element by simply passing it to a procedure.
Using IsElement(mylist("b")) before calling the procedure/function helps to avoid the problem, but that shouldn't be necessary, as the programmer expects an error if the List element does not exist.

Sub Initialize
Dim mylist List As String
' Msgbox mylist("b") 'properly results in error "List item does not exist"

Call tst(mylist("b")) 'erroneously creates "b" list element

' Msgbox Isnull(mylist("b")) 'erroneously creates "b" list element

Msgbox mylist("b") 'shows "" as list value for "b" instead of an error

End Sub

Sub tst(tmp)

End Sub



Spanky said...

Sorry Andrei, but I must disagree with you.

Any call to a list element by tag (ie: myList(tag$) ), even if the element does not exist, will cause a creation attempt of the list element. Granted, this can open the door to some potential problems, especially if the list is comprised of complex types, classes, or product objects.

That is the reason the IsElement() function exists.

In your code example, when you pass a potentially non-existing list element to your function:

Call tst(mylist("b"))

You must remember that LotusScript, by default, passes parameters by reference. The interpreter evaluates the argument first:
before passing it to your method. Which means that the interpreter attempts to create an element in your list for the tag "b" before passing it to your method.

Now, if your list was comprised of objects or classes, I'm willing to bet you would get the error you were expecting.

Dim myList as NotesDocument

Function tst(source As NotesDocument)

.. code here

End Function ' tst

call tst(myList("b")) ' error here?

NOTE: I have not tested this.
If an error occurs here, I'd be willing to bet that it is due to the attempt to pass an uninstantiated (not even Nothing) object instance to a method which is expecting an object.

My suggestion is to structure your code to always test if an element is a member of the list whenever the possibility of ambiguity exists:

If isElement(myList("b")) Then
' element exists, do something
' element does not exist, do something else
End If

Hope this helps!

Anonymous said...

I think of Lotuscript lists kind of like the genie in Aladdin -- "phenomenal cosmic powers -- itty-bitty living space".

What they do, they do well, what they don't they don't.

Now if we could have TYPED lists or declare a function that returns a list? A TYPED list?

Oooo. It just gets me all hot and bothered.

Then I end up using a collection class of some kind to do what I want.

C'est la guerre.

Spanky said...

@2 - Hey Jack, try this:

Public Class StringList
Public Content List As String

Public Sub New()
' do nothing
End Sub

Public Sub Delete()
Erase Me.Content
End Sub
End Class

It is nothing more than a wrapper class for a list. Why use it? So you can have function return it:

public Function foo() As StringList

Dim result As StringList

Set result = New StringList()

result.Content("bar") = "Jabberwocky"

Set foo = result

End Function

You can, of course, extend the logic to cover much more than lists of strings.


Anonymous said...

Aye, I agree with you Andrei -- however, I'm seeing everything through the lens of JavaScript, so generating an error instead of creating a new element just makes sense to me!