LotusScript

May. 5th, 2011 11:43 pm
[personal profile] barking_iguana
My word LotusScript is full of annoying quirks. Then again, so is every language, but returning to LS (as a hobbyist) after all these years, I can really see why people who are used to other languages find this one so annoying. It's just similar enough so you think you know what you need to do, but until you get used to it, you don't.

I just finished some code that I wrote most of last week but was too sick to want to finish until now. Of course, I was slowed down by a constant argument in my head between writing and documenting code that I'll only have to modify slightly for later aspects of the project one the one hand and making it as short and specific as possible on the other. I mostly chose to make it either general or easily generalizable but to leave out documentation of anything that will be clear to me later, as it's highly unlikely anyone else will ever have to look at the code (unless you click below). For myself, I'd rather see more lines of actual code on the screen rather than have screen real estate taken up by English.

The project is to make an online reference site for complex games, with Imperialism II being my first go. It has technologies, usually with one or more prerequisite technologies needed before you can research the one you're looking up. The code (called an Agent) I wrote takes all the technologies (stored in Notes Documents) and their immediate prerequisites (which I already manually put on the documents in a multi-value field) and calculates lists of all prerequisites, direct antecedents, and all antecedents and puts them in three more multi-value fields on each document.

Of course, LotusScript isn't really native to Notes/Domino, despite that being it's major use. For instance, in Notes, there's no difference between a scalar value and a list--a scalar is simply a list with one element. (The Notes UI can make a distinction, but the back end does not.) In LotusScript, there are arrays and scalars. (There's also something called a List, but that's another animal--an associative array with strings as keys.) And the collections returned by the Notes/Domino class library are not passable to the ForAll iterator used on collections native to LotusScript. They do come with their own iteration methods, at least.

Sometimes it pays to use the Evaluate method, which lets you pass a string to the Notes Formula language compiler. That language is beautifully integrated with the platform, but isn't rich enough to do most jobs that call for iteration or recursion.

This code itself is written in the form of an Agent, which is a Domino "design element". You can think of it as a containing class, in that it provides appropriate scope for variables and methods that you'll want to access from more than one place and Initialize and Terminate methods that will automatically run if they have any code in them.

Ten years ago, I was a proponent of not giving elements of collections ordinal numbers (by placing then in compiler-friendly arrays) unless those ordinal numbers actually meant something or the performance hit of using other kinds of collections was significant. I have a hunch now I wouldn't have to push for that because it's what everyone does, but I don't know. In any case, I make extensive use of those Lists I mentioned above, because aside from Arrays, they're the only native collection LotusScript has. Which brings up another irksome quirk...

Variables can be undeclared (but not if you're smart enough to use Option Declare), declared as a specific data type, or declared as a Variant, which means at least you have to spell it right, but there's no compile-time type checking. Unfortunately, Arrays and Lists can neither be passed nor accepted by user-written methods when declared as such. Library functions do oft-times take and/or return them, and there's no problem with Elements of collections, but you can't assign a whole collection to a variable declared to be of any type other than Variant. So I use variants a lot and comment the type they're supposed to hold.

Anyway, if you've read this far, I'd love any comments you might have on the above or on the code.

Option Public
Option Declare


Dim db As NotesDatabase
Dim LevelTechs(0 To 7) As NotesDocumentCollection
Dim AllPrereqs As TechList
Dim DirectPrereqs As TechList
Dim AllDependants As TechList
Dim DirectDependants As TechList


Class TechList
  Public FieldName As String
  Public Techs List As Variant ' String() or String

	
  Sub New (Fname As String)
    FieldName = Fname
  End Sub

	
  Sub MakeReverse (BaseList As TechList)
    Dim BaseTech As string

    ForAll FocusSet In BaseList.Techs ' String() or String
      BaseTech = ListTag(FocusSet)
			
      If IsArray(FocusSet) Then
        ForAll FocusTech In FocusSet
          AddOrMake BaseTech, FocusTech
        End ForAll
      Else
        AddOrMake BaseTech, FocusSet
      End If

    End ForAll
  End Sub

	
  Sub AddOrMake (NewVal As String, Tag As Variant) 'Tag is String
    Dim StartArray(1 To 2) As String
		
    If Not IsElement(Techs(Tag)) Then
      Techs(Tag) = NewVal
    ElseIf IsArray(Techs(Tag)) Then
      Techs(Tag) = ArrayAppend(Techs(Tag), NewVal)
    Else
      StartArray(1) = Techs(Tag)
      StartArray(2) = NewVal
      Techs(Tag) = StartArray
    End If
  End Sub
		
		
  Sub MakeField (Doc As NotesDocument)
    Const MACRO = "@SetField(DeleteThis; @Sort(@Unique(@GetField(DeleteThis))))"
    REM "DeleteThis" is explained below.
		
    Dim FocusTech As String
    FocusTech = Doc.GetItemValue("Name")(0)
    If IsElement(Techs(FocusTech)) Then
      Doc.ReplaceItemValue FieldName, Techs(FocusTech)
      Doc.ReplaceItemValue "DeleteThis", FieldName
      REM The field DeleteThis contains the name of the real field to be accessed.
      REM This allows MACRO to be pre-compiled, which speeds execution.
      Evaluate MACRO, Doc
      Doc.RemoveItem("DeleteThis")
    Else
      Doc.ReplaceItemValue FieldName, "none"
    End If
  End Sub

	
End Class


Sub Initialize
  Dim s As New NotesSession
  Set db = s.CurrentDatabase
	
  SetLevelTechs
  SupressNone
  FindPrereqs
	
  Set AllDependants = New TechList("AllDependants")
  AllDependants.MakeReverse AllPrereqs
  Set DirectDependants = New Techlist("DirectDependants")
  DirectDependants.MakeReverse DirectPrereqs
	
  SaveDocs
End Sub


Sub SetLevelTechs ()
  Dim level As Long
  Dim searchstring As String
	
  searchstring = |Form = "Imp2Tech" & Period = |
  For level = 0 To 7
    Set LevelTechs(level) = db.Search(searchstring & CStr(level), nothing, 0)
  Next
	
End Sub


Sub SupressNone
  Dim Doc As NotesDocument
	
  Set Doc = LevelTechs(0).GetFirstDocument
  Do
    Doc.ReplaceItemvalue "DirectPrereqs", ""
    Doc.Save True, False
    Set Doc = LevelTechs(0).GetNextDocument(Doc)
  Loop Until Doc Is Nothing
	
  Set Doc = LevelTechs(1).GetFirstDocument
  Do
    If Doc.GetItemValue("DirectPrereqs")(0) = "none" Then
      Doc.ReplaceItemvalue "DirectPrereqs", ""
      Doc.Save True, False
    End If
    Set Doc = LevelTechs(1).GetNextDocument(Doc)
  Loop Until Doc Is Nothing
	
End Sub


Sub FindPrereqs ()
  Dim FocusLevel As Long
  Dim LevelColl As NotesDocumentCollection
  Dim doc As NotesDocument
  Dim FocusTech As String
  Dim OnDocPrereqs As Variant ' String()
  Dim OldTechs As Variant ' String()
  Dim NewTechs As Variant ' String()
  Dim StartArray (1 To 2) As string
	
  Set DirectPrereqs = New TechList("DirectPrereqs")
  Set AllPrereqs = New TechList("AllPrereqs")
	
  For FocusLevel = 0 To 7
		
    Set LevelColl = LevelTechs(Focuslevel)
    Set doc = LevelColl.Getfirstdocument()
    Do
      FocusTech = doc.Getitemvalue("Name")(0)
      OnDocPrereqs = doc.GetItemvalue("DirectPrereqs")
      DirectPrereqs.techs(FocusTech) = OnDocPrereqs
      AllPrereqs.techs(FocusTech) = OnDocPrereqs
			
      ForAll TechName In doc.GetItemValue("DirectPrereqs") ' String
				
        If Not TechName = "" Then
          OldTechs = AllPrereqs.techs(FocusTech)
          NewTechs = AllPrereqs.techs(TechName)
					
          If IsArray(OldTechs) Then
            AllPrereqs.techs(FocusTech) = ArrayAppend(OldTechs, NewTechs)
          ElseIf IsArray(NewTechs) Then
            AllPrereqs.techs(FocusTech) = ArrayAppend(NewTechs, OldTechs)
          Else
            StartArray(1) = OldTechs
            StartArray(2) = NewTechs
            AllPrereqs.techs(FocusTech) = StartArray
          End If						
        End If
				 
      End ForAll
			
      Set doc = LevelColl.Getnextdocument(doc)
    Loop Until doc is Nothing
		
  Next
	
End Sub


Sub SaveDocs
  Dim NewFields (1 To 3) As TechList
  Dim Level As Long
  Dim Coll As NotesDocumentCollection
  Dim Doc As NotesDocument
  Dim FocusTech As string
	
  Set NewFields(1) = AllPrereqs
  Set NewFields(2) = DirectDependants
  Set NewFields(3) = AllDependants
	
  For Level = 0 To 7
    Set Coll = LevelTechs(Level)
    Set Doc = Coll.GetFirstDocument()
    Do
			
      ForAll TList In newFields ' TechList
        TList.MakeField Doc
      End ForAll
      If Doc.GetitemValue("DirectPrereqs")(0) = "" Then
        Doc.ReplaceItemvalue "DirectPrereqs", "none"
      End If
			
      Doc.Save True, False
		
      Set Doc = Coll.GetNextDocument(Doc)
    Loop Until Doc Is Nothing
  Next
End Sub

Date: 2011-05-06 04:35 am (UTC)
From: [identity profile] chemoelectric.livejournal.com
Due to programming largely in OCaml, I am annoyed by having to declare variables. Typically languages that have static types require type declarations, but OCaml requires it only if you are declaring public interfaces for modules, and rarely indeed needs it otherwise. Yet the static type safety is much stronger than usual (and there are no workarounds or "casts").

I'm also annoyed by mutable structures, partly because the behavior of a function on immutable structures can be read somewhat from its type signature, and I can have the compiler print out the type signature for me as a check on what I wrote (or I can code something like "print_string thing_to_check" and the type signature of the thing will be given in the resulting error message, assuming the type isn’t "string").

Loops can be annoying compared to recursion, due to their association with mutable values. Also recursions often have shorthand forms that can be written in-line as function calls, though these can be hard to keep straight.
Edited Date: 2011-05-06 04:36 am (UTC)

Date: 2011-05-06 12:12 pm (UTC)
From: [identity profile] barking-iguana.livejournal.com
I make far too many typos to want to be allowed undeclared variables. Without that "Option Declare" (or the VB version Option Explicit, which does the same thing) I could do so, but it would take me far longer to debug.

Date: 2011-05-06 02:25 pm (UTC)
From: [identity profile] chemoelectric.livejournal.com
You haven’t used a language with type inference, most likely. It’ll tend to catch your typos, because most often they’ll cause type errors. Also you might not be doing many of the same things with your variables, since most of them would actually be constants.

One of the things I like about OCaml is that, when I have working code but want to rewrite a bit of it, the tendency is that the new code works the first time I succeed in compiling it. It is amazing how many programming errors are catchable by actually paying attention to types.

(The compiler has an annoying habit just to say "syntax error" for syntax errors, but this is partly because you'll probably use the camlp4 preprocessor for at least this, that it gives a more complete message on syntax errors.)

Date: 2011-05-07 07:57 pm (UTC)
From: (Anonymous)
I've never used OCaml, but I'll second most of your comments. The only one I might quibble on is that some people seem to like recursing too much where a while loop does the job more explicitly.

Date: 2011-05-08 07:48 am (UTC)
From: [identity profile] chemoelectric.livejournal.com
The enumerations in Batteries help with those situations, not to mention the full repertoire of "iter", "map", and "fold" functions for enumerations, lists, arrays, etc., etc. A for-loop, for instance, becomes "iter", "map", or "fold" over an enumeration of integers; makes working with array-like structures much easier.

Tail recursion is just about the same thing as a while-loop, with function parameters (which can be immutable) taking the place of loop variables (which cannot be immutable).

Date: 2011-05-06 04:57 am (UTC)
avram: (Default)
From: [personal profile] avram
Did you use <pre>...</pre> tags when you pasted the code into the LJ post? I know it works on LJ, because I've used it myself.

Date: 2011-05-08 07:49 am (UTC)
From: [identity profile] chemoelectric.livejournal.com
MUCH easier to read.
From: [identity profile] tawmess.livejournal.com
Hello, Dvd -- glad to cross-paths with you here, and welcome and match your efforts...

I too am now a hobbist, with LotusScript -- after a 5-year break from a 15 year career as a consultant for Lotus Notes programming -- toying with the idea of re-honing my expertise with LotusScript...

So I welcome your public sharing, of your LotusScript efforts, here -- and will dig deeper through your code here, and share what I can, in further comments.

Thank you, for the fun here!

From: [identity profile] barking-iguana.livejournal.com
I think it would have been better to have the Techlists full of arrays, even when there was only one value for the tag. But I didn't realize that until I'd already written the code to handle the possibility of scalars.

Profile

Dvd Avins

March 2020

S M T W T F S
123 4567
891011121314
15161718192021
22232425262728
293031    

Style Credit

Expand Cut Tags

No cut tags
Page generated Mar. 16th, 2026 12:07 pm
Powered by Dreamwidth Studios