Great article, I find this feature very useful too, here's my %ZLANGC00:

 ; %ZLANGC00
 ; custom commands for ObjectScript
 ; http://localhost:57772/csp/docbook/DocBook.UI.Page.cls?KEY=GSTU_customize
  Quit    

/// Execute Query and display the results
/// Call like this:
/// zsql "SELECT TOP 10 Name FROM Sample.Person"
ZSQL(Query)
    #Dim ResultSet As %SQL.StatementResult
    Set ResultSet = ##class(%SQL.Statement).%ExecDirect(, Query)
    Do ResultSet.%Display()
    Quit

/// Move user to a namespace of his choice
/// Call like this:
/// zm "s"
ZM(Namespace)
    Do MoveToNamespace^%ZSTART(Namespace)
    Quit
    
/// Move to Samples namespace and set a as a Samples.Person object
/// Set b as %ZEN.proxyObject
/// Set c as %Object
ZO(move = 1) Public
    ZN:move "SAMPLES"
    If ##class(%Dictionary.CompiledClass).%ExistsId("Sample.Person") {
        Set p = ##class(Sample.Person).%OpenId(1)
    }
    Set a = ##class(%ZEN.proxyObject).%New()
    Set b = {}
    Quit

and related %ZSTART:

%ZSTART() {
    Quit    
}

/// This entry point is invoked on user login
/// offering user to choose a namespace
LOGIN() Public {
    Set Timeout = 3
    Write "Namespace <" _ $Namespace _ ">: "  
    Read Namespace:Timeout // Get value of a Namespace variable
    Quit:Namespace=""
    Do MoveToNamespace(Namespace)
}    

/// Does actual moving to a chosen namespace
/// This is an entry point, for cases where
/// Namespace value is already aquired
MoveToNamespace(Namespace = "") Public {
    Set Timeout = 3
    #Dim List As %ListOfDataTypes
    Set List = $$GetNamespaceList(Namespace)
    Set Count = List.Count()
    
    If Count = 1 {
        Set Choice = 1
    } ElseIf Count > 1 {
        Do DisplayList(List)

        // If there is less then 10 results, then we need only 1 digit
        // Otherwise we need 2 digits
        // It is assumed that no more then 99 results would be returned
        Read "Select number <1>: ", Choice#$Select(Count < 10:1, 1:2):Timeout

        // If the user entered nothing or not a valid number
        // we select first namespace in a list to go to
        Set:((Choice = "") || ('$IsValidNum(Choice, 0, 1, Count))) Choice = 1
    } Else {
        // No namespaces found
        Quit
    }
    
    Zn List.GetAt(Choice)
}

/// Get all availible namespaces that satisfy
/// "Name %STARTSWITH Namespace" condition
/// as %ListOfDataTypes
GetNamespaceList(Namespace = "") {
    New $Namespace
    Set $Namespace = "%SYS"

    #Dim List As %ListOfDataTypes
    #Dim ResultSet As %SQL.StatementResult
    Set List = ##class(%ListOfDataTypes).%New()

    Set UserCondition = "%UPPER(Name) %STARTSWITH %UPPER(?)"  // Or [ if you wish
    Set Condition="(" _ UserCondition _ ") AND (SectionHeader='Namespaces') AND (%UPPER(Name)!='%ALL')"
    Set SQL = "SELECT Name FROM Config.Namespaces WHERE " _ Condition

    Set ResultSet = ##class(%SQL.Statement).%ExecDirect(, SQL, Namespace)
    While ResultSet.%Next() {
        Do List.Insert(ResultSet.%Get("Name"))
    }

    Quit List
}

/// Display %ListOfDataTypes in a format:
/// 1    item
/// 2    item
/// ...
DisplayList(List) {
    #Dim List As %ListOfDataTypes
    Write !
    For i = 1:1:List.Count() {
        Write i, $C(9), List.GetAt(i), !
    }
}

Github repo.

Both BuildValueArray and LogicalToDisplay work with serialized form of %ListOfDataTypes object - $list string:

do ##class(%ListOfDataTypes).BuildValueArray($lb("a","b","c"), .out)
zw out

Outputs:

out(1)="a"
out(2)="b"
out(3)="c"

To sum up:

  • %ListOfDataTypes - object
  • serialized %ListOfDataTypes  - $list
  • $list - a string (with special properties, but not an object)
  • %List - $list

Yes, I'm aware of that.  There is not that much records (hundreds of thousands tops). Still, decided to do a comparison:

ClassMethod Time(count = 100000000)
{
    Set str = "111111111111111111111111111111111111111111111111111111111"
    Set time1 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $zcrc(str, 7)
    }
    Set time2 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $System.Encryption.MD5Hash(str)
    }
    Set time3 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $System.Encryption.SHA1Hash(str)
    }
    Set time4 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $System.Encryption.SHAHash(256, str)
    }
    Set time5 = $P($h, ",", 2)
    Write !,"CRC: ",time2-time1,!,"MD5: ",time3-time2,!,"SHA1: ",time4-time3,!,"SHA2: ",time5-time4
}

It outputs the following results:

CRC: 14
MD5: 72
SHA1: 119
SHA2: 140

Yes.  Use resources instead. Read the docs. $ZPARENT value is also a "resource" that's getting created with the process, so children processes can communicate events to a parent process easily. As processes you want to pass messages between are not parent and child,  you need to create and use explicitly named resources.

You can use %SerialObject for that:

Class Utils.Serial Extends %SerialObject
{

Property Payload As %String;

/// zw ##class(Utils.Serial).Test()
ClassMethod Test(input As %String = {$lb(1,2,3,",",5)}) As %String
{
    
    set obj = ##class(Utils.Serial).%New()
    set obj.Payload = input
    do obj.%SerializeObject(.str)
    kill (str)
    
    set obj = ##class(Utils.Serial).%Open(str)
    return obj.Payload
}
}

For example:

zw ##class(Utils.Serial).Test("1,2,3,,,5")
>"1,2,3,,,5"

zw ##class(Utils.Serial).Test($lb(1,2,3,",",5))
>$lb(1,2,3,",",5)