Speaking of C_OBJECT: Roll your own types

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Speaking of C_OBJECT: Roll your own types

David Adams-4
I touched on something in another thread today I want to follow up on. I
was noting that C_OBJECT is great, but that it doesn't add a bunch of
helpful and fundamental features to the language and Compiler. Of these, at
least one can more-or-less be improved with custom code: a richer concept
of type.

In the 4D world, the basic concept of type is quite limited: some simple
types, arrays of simple types, and now name-key hierarchies that can be
stringified as JSON. What we don't have is the ability to declare new types
and get the language to recognize them as types or the Compiler to catch
type-related errors. For example, wouldn't it be nice to have a type called
"Currency" where you defined levels of precision? And for it to recognize
when a raw real is being passed by mistake? That's a basic (but common)
example. I'm sure that we each have a bunch of more complex data structure
implemented on way or another that *act* like types but without any
automated enforcement. Since people seem to be diving into C_OBJECT these
days, it's a good time to thinking about and discussing setting up a bit of
infrastructure to make your life easier and your code more reliable.

Okay, so here's a kind of example. I've got a variety of unit tests that
generate findings. Each finding has several properties. Here's an example
finding:

{
"pass": true,
"group": "MethodInfo_CreateObject",
"description": "MethodInfo_CreateObject: Good: ID",
"expected": ">0",
"actual": "1524"
}

Any particular test suite may bundle dozens of individual test results.
That's all pretty easy to understand, and a totally natural use of C_OBJECT
or JSON. If you look at the JSON snippet above, it's easy to think of it as
a record with four fields. That's a custom data structure. There's code to
create, write, and read the structure. Now what happens if I pass into the
reader some _other_ kind of object? Then things blow up. (Well, you could
catch that error, but you see the point.) What if the overall structure had
a type? That's easy to do with a simple header like this one:

"header": {
"type": "Test_Results",
"version": 1,
"name": ""
} --- findings go in a body section here ----

So, the TestResult_ResultsToHTML($results) routine can first test that the
type = Test_Results and that the version is supported. It's not that rare
to evolve a data structure and add/remove/retype/rename elements. A version
number makes it easy to detect when you need to shift your reader code or
reject an input. If you use a header like this with every custom action,
you then can write a generic test routine:

Object_IsSupported($object;"Test_Results";1)

That's a simple version of the routine - mine optionally supports arrays of
allowed types and versions, but you get the idea.

So that's about it: Add a header to each and every object with a type and
version. (The name I include is an instance value - it's just handy.) Then
let your code verify that the type and version are supported before
continuing. In my universe, the header section belongs to the object
management code and the body part belongs to whatever module defines and
controls the object. (Meaning, the header is always read/written through a
bunch of generic object handling routines.)

Oh, and with this approach, you can also do some interesting things with
delegation of actions. For example, you can have a type-specific converter,
like

$text:=TestResult_FindingsToText($results)

..but if there isn't a type-specific implementation, you can fall back on a
generic one:

$object:=Object_BodyToText($results)

...which can stringify everything but the object header, for example.

Getting a bit further into this, if you set up an object type register, you
can then pick which way delegation flows. For example, if you register

ObjectType_Register("Test_Results";BodyToTextMethodName;"TestResult_FindingsToText")

...then you can call

$object:=Object_BodyToText($results)

...it can interrogate the catalog, find that the TestResult_FindingsToText
is registered to handle conversions *of this type of object*, and execute
the method.

And getting a bit further in, if you write a code scanner to inspect your
source, you can start to detect places where the wrong type is being passed
in your code *at rest.* Bring back SanityCheck!

Some/many of you have already done what I've mentioned above with
ObjectTools, I'd assume, and might like to offer some commentary.

Here's some crude V15 code that shows how easy it is to add a header. (I'm
actually using NTK's JSON, but that's an implementation-level point, not a
design-level point.)

OB SET($header;"Object_Type";"TestResult")
OB SET($header;"Object_Version";1)
OB SET($header;"Object_Name";"Array function unit tests")

C_OBJECT($object)
OB SET($object;"header";$header)
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************