Ask Reuben
Reflection
What is Reflection?
How can I write more generic code?
How can I write library functions to work with all arrays?
The Reflection API was introduced in Genero 4.00 allowing you to introspect and use program elements that are not known at compile time. The reflect package has a value class to inspect and modify variables, a type class to inspect type, and a method class to inspect methods. It is useful to write generic code in conjunction with base.SqlHandle and ui.Dialog class methods. The base.SqlHandle allows you to write generic database interaction, ui.Dialog allows you to write generic user interface code, the new reflect package is the missing piece in the puzzle that allows you to write generic code that operates with different datatypes. A reflect.Value object is the runtime representation of a variable that has methods to tell you about itself. It can also be thought of as a pointer to a variable and is useful to pass to/from generic routines. An example I had early on was …
… that can be called with code like … So note in the call You can tweak that code as noted in the comments to make it more forgiving but hopefully you can see that that 30 line function allows you to take any SELECT statement and populate a dynamic array with the result set of the SQL statement in one line of code!. Other examples I have to show the power of reflection are these examples to manipulate arrays. Something like the following … … allows you to delete multiple rows from an array with a one line function call. Similarly the following … … allows you to append two arrays together. In all cases passing to the functions, a pointer to the array achieved via use of reflect.Value.valueOf call e.g. If you have not already, I would encourage you to study the reflect package and note how you can reduce the number of lines of code in your application and make it easier for junior developers to be more productive by adding some useful methods to your libraries. Neil did a presentation at the last WWDC https://4js.com/wwdc-21-online-conference-archive/#tab-id-3 which includes some of these examples and more. I think there is a lot of potential using the reflect package to write some good generic code. One thing I wish is it was simpler to type reflect.value.valueOf(). I think if I had a production system I might have a pre-processor macro of
IMPORT reflect
FUNCTION sql2array(sql STRING, r_arr reflect.Value) RETURNS()
DEFINE h base.SqlHandle
DEFINE column_idx, row_idx, column_count INTEGER
CALL r_arr.clear()
LET h = base.SqlHandle.create()
CALL h.prepare(sql)
CALL h.open()
LET row_idx = 0
WHILE TRUE
CALL h.fetch()
IF sqlca.sqlcode = NOTFOUND THEN
EXIT WHILE
END IF
CALL r_arr.appendArrayElement()
LET row_idx = row_idx + 1
VAR r_row = r_arr.getArrayElement(row_idx)
LET column_count = h.getResultCount()
FOR column_idx=1 TO column_count
-- Assumes table and array structure match, you could modify so it
-- was more forgiving by using r_rec.getFieldByName, h.getResultByName etc
CALL r_row.getField(column_idx).set(reflect.Value.copyOf(h.getResultValue(column_idx)))
END FOR
END WHILE
CALL h.close()
END FUNCTION
IMPORT reflect
&define TEST_TABLE_STRUCTURE idx INTEGER, dat CHAR(3), dmy DATE
MAIN
DEFINE arr DYNAMIC ARRAY OF RECORD
TEST_TABLE_STRUCTURE
END RECORD
CONNECT TO ":memory:+driver='dbmsqt'"
CREATE TABLE foo( TEST_TABLE_STRUCTURE)
INSERT INTO foo VALUES(1,"AAA", TODAY)
INSERT INTO foo VALUES(2,"BBB", TODAY)
CALL sql2array("SELECT * FROM foo", reflect.Value.valueOf(arr))
DISPLAY arr[1].*
DISPLAY arr[2].*
END MAIN
CALL sql2array("SELECT * FROM foo", reflect.Value.valueOf(arr))
, the reflect.Value.valueOf
method is effectively creating a pointer to the variable arr
which is being passed to the function sql2array()
. The sql2array
function is then populating the arr
dynamic array variable with the result of the select sql statement that is the first parameter.
FUNCTION array_delete_range(r_arr reflect.Value, from INTEGER, to INTEGER)
DEFINE idx INTEGER
-- Will need some error handling at beginning
IF r_arr.getType().toString() MATCHES "DYNAMIC ARRAY*" THEN
-- OK to continue
ELSE
RETURN -- Throw exception when able
END IF
IF from >= 1 AND from <= r_arr.getLength() THEN
-- OK to continue
ELSE
RETURN -- Throw exception when able
END IF
IF to >= 1 AND to <= r_arr.getLength() THEN
-- OK to continue
ELSE
RETURN -- Throw exception when able
END IF
IF from <= to THEN
-- OK tp continue
ELSE
RETURN -- Throw exception when able
END IF
FOR idx = to TO from STEP -1
CALL r_arr.deleteArrayElement(idx)
END FOR
END FUNCTION
FUNCTION array_appendto(r_arr1 reflect.Value, r_arr2 reflect.Value, start INTEGER)
DEFINE i INTEGER
DEFINE row1, row2 reflect.Value
-- add error handling
IF r_arr1.getType().toString() = r_arr2.getType().toString() THEN
-- Same type
ELSE
-- Throw error when able
END IF
IF start >=0 AND start <= r_arr1.getLength() THEN
#OK (Note if start=0 then append to end)
ELSE
-- Throw error when able
END IF
FOR i = 1 TO r_arr2.getLength()
LET row2 = r_arr2.getArrayElement(i)
IF start = 0 THEN -- append at end
CALL r_arr1.appendArrayElement()
LET row1 = r_arr1.getArrayElement(r_arr1.getLength())
ELSE
CALL r_arr1.insertArrayElement(start+i-1)
LET row1 = r_arr1.getArrayElement(start+i-1)
END IF
CALL row1.set(row2)
END FOR
END FUNCTION
CALL arr_delete_range(reflect.Value.valueOf(arr), 3,5) --delete 5 rows starting at row 3
CALL array_appendto(reflect.Value.valueOf(arr1),reflect.Value.valueOf(arr2), 0) -- append arr2 to arr1
&define ptr(p1) reflect.Value.ValueOf(p1)
so that reading the code I see ptr(array_variable)
and read it as pointer to an array variable!