There's a language enhancement I'd like to see in Delphi, but I'm not sure what exactly it is. I can only point to where it shows up as a missing for me. Feel free to comment on what it is that I seem unable to point to directly. Perhaps it even has some kind of name; I really don't know.

There's a language enhancement I'd like to see in Delphi, but I'm not sure what exactly it is. I can only point to where it shows up as a missing for me. Feel free to comment on what it is that I seem unable to point to directly. Perhaps it even has some kind of name; I really don't know.

I was writing a bit of code where I needed to create a couple of methods that everybody runs into now and then: LoadXxxxFromYyyy and SaveXxxxToYyyy.

Xxxx is usually an object of some kind and Yyyy is some kind of container object like a File, Stream, Ini, Form, Cache, Buffer, ... whatever. We've all written plenty of these, right?

Now, since Xxxx is usually an object, the things that are being copied out and in are typically PROPERTIES -- and usually Public or Published properties at that.

What I'd LIKE to see is the ability to write a single bi-directional copy method like this:

CopyEx( aVal1 : T1; aCollection : T2; dirFlg : Boolean );

Unfortunately, this is impossible today because of "limitations" in how properties can be used. If you're not quite clear what I'm talking about here's a little more detail.

Say you have a property like this:

property Abc : string read FAbc write FAbc;

To create the Load and Save methods, one needs to say this in one of the methods (eg, LoadAbcFromIni):

Abc := ini.ReadString( 'secnm', 'Abc', '' );

while the other one (eg, SaveAbcToIni) needs to say:

ini.WriteString( 'secnm', 'Abc', Abc );

So the CopyEx might look something like this:

CopyEx( Abc, ini, dirflg);

If dirflg were True, then the copy goes left-to-right (==>), and if dirflg were False then the copy goes right-to-left (<==). This would effectively replace the two totally asymmetric assignment statements that I showed just above.

But it won't work in the latter case, because property names can only be assigned a value if they appear on the left-hand side of an assignment statement.

These Load/Save methods are a PITA to write, especially if there are a bunch and the data types vary. But there are lots of other situations where you have to write code that does the same kind of thing - copying several values from A to B in one part of the code, and then copying the same values from B to A somewhere else. (eg., what Assign methods do)

This can work for variables if you're careful, but not properties.

Actually, you'd need several overloaded methods to handle different types, like ordinals vs. strings (arrays?) vs. Arrays vs. records vs. classes vs. references, and so forth.

Maybe we just need a special "magic function" added to the language that slashes the Gordian knot in this case and lets one single expression (or pattern) be used for an entire class of types that exist to support the exact same semantics: copy a value 'V' from/to a collection object of some kind that probably needs to refer to 'V' by name, in a way that works for whatever type 'V' might be.

Why would this be useful?

Imagine tagging some properties with Attributes that say they're available to be streamed in and out (or serialized, or whatever you want to call it). Then you can write a generic method with a two-line loop that looks like the CopyEx(...) method and it's able to perform the copy between aligned variables or properties in any two objects based on whatever metadata you want to provide in the Attributes. Even if there's, say, 250 properties in a class to be copied back and forth, you'd only need one simple bidirectional CopyEx(...) method to handle both Load and Save functions.

Now, as I said, I know why this can NOT be done today (at least, not easily) -- it's because of syntactic limitations in the use of property names. I actually don't have a suggestion for fixing this other than rto emove these "limitations". But I can understand why that may not be practical.

The question is: what changes can be made in the language to support this kind of mechanism?

The limitation seems to be an asymmetry in the language syntax that exists for no particularly justifiable reason (to me, anyway). Interestingly, this specific problem in Delphi does not arise in any other languages I can think of, although most languages do have their own unique asymmetries.

For example, I know that over the years, the C++ standards folks have bent over backwards to eliminate asymmetries between "lhs" and "rhs" expressions. Some are simply unavoidable; others are there "just because". It's the latter ones they've done their best to eliminate.

What can be done to remedy this situation in Delphi? I think this is something that would make a lot of code simpler in a lot of places, because this "pattern" of copying data (consisting of blocks of properties) back and forth is fairly ubiquitous in our work.

What would it take to enable this?

((Now I can't help but think ... somebody is going to say, "But you CAN do that already! This library already supports it!" Ok, well, why is it in a 3rd-party lib and not in the base product?))

Comments

  1. Paul TOTH this has nothing at all to do with INI files. They're just an example b/c I happen to be doing work on a 12-yo app (from 2006) that DOES use them. Did yhou notice the mention of working with Forms? Or don't you use them in 2018 either? ;-) Just substitute your favorite container there and make a guess as to how much coding it will save you to stream out/in objects with 25/50/100/200 properties in them.

    ReplyDelete
  2. David Schwartz
    If I have 200 properties to stream in an object, I use RTTI, and it can be a sub-object or sub-record to have only the properties I need to stream.

    type
    TMyData = record
    MyProp: TMyType;
    end;
    TMyObject
    private
    FData: TMyData; // this will be streamed in/out with RTTI
    public
    property MyProp: TMyType read FData.MyProp write FData.MyProp;

    What you are requesting for can probably be done with attributes.

    type
    TForm1 = class(TForm)
    public
    [LoadWith(MyLoader) SaveWith(MySaver)]
    property MyID : string read GetMyID write SetMyID
    end;

    ReplyDelete
  3. Paul TOTH I still think you're missing my point. One does not "stream" data between forms, database record buffers (ie., class instances that contain single-record data), and objects in collections, the way you're thinking. While it's certainly possible, I've never seen anybody actually do it.

    Look, Delphi has data-aware controls that make the connection between a database table and the mechanism that copies data from a "current record" into the fields on a form pretty much invisible. This isn't some kind of "streaming interface" in the same sense that you'd serialize data that's sent over the wire somewhere.

    I'd assert that we've been spoiled by this convenient interface mechanism. The problem is, if you want to implement something like an MVC or MVVM or some other multi-part model, the classic way of using Delphi TDataSet objects connected to data-aware controls is unworkable. You have to manage those connections yourself.

    So the Delphi language wizards created something called LiveBindings. These are also not a normal kind of "streaming" interface.They're a little more generic than what typical data-aware controls use, but they're not very general-purpose (at least, they don't seem that way to me). They're an indirect mechanism that's not technically integrated into the IDE (ie., it's not mandatory), but is far more useful using IDE resources than not.

    Anyway, what I'm proposing is actually rooted in a simple concept.

    There are two sides to every data transfer: (1) in and out of data buffers in an object (ie., individual fields / properties); and (2) the other side of that equation -- whatever is producing / consuming the data being loaded in and out of those fields.

    TDataSets pair with Data-aware objects to make this invisible.

    But any given object only supports half of this mechanism: the first one described above.

    What I'm proposing is a mechanism to handle the other half as an extension of Property definitions. It can be used to build a streaming mechanism if you want, but it's not limited to that. Mainly it's to allow two complimentary objects to interact more easily without the programmer having to write individual lines of code for every data element that needs to be copied back and forth between them.

    I suppose you could go to the trouble to use a JSON streaming interface to copy data in and out of forms, but why? Just because they're easy to drop-in?

    The thing to notice here is that we think of "streaming" as a solution to the second part of that equation I mentinoed earlier. But it highlights part of the problem: you need to build that interface for a common "channel", whether it be JSON or a TStream or SOAP or an INI file or whatever. Which is the whole point of having streaming logic -- so you don't have to re-write it each time.

    Let's say I have some large aggregate object, like TBigObj, and it is composed of, say, four smaller objects, TObj1, TObj2, TObj3, and TObj4. TBigObj has 200 fields in it, because each of TObj1..4 have 50 fields in them. And they're all different objects that for various reasons may not have 100% isomorphism between the types of corresponding fields in TBigObj and what's in the objects that contribute to it. Meaning a given field might be stored as a string in one and a float or integer in the other. And they might also be in different orders.

    You'd need to write code to copy each TObj_n into and out of TBigObj, right? So you need to write a total of 400 assignment statements scattered across a bunch of separate methods.

    You're not "streaming" anything here in the sense that you'd use JSON or SOAP or something like that to copy the data between the objects. That would be silly. But you're also not getting any benefit from either TDataSet / Data-aware controls or even LiveBindings.

    ReplyDelete
  4. You have to write all of those copy methods by hand!

    And IF you're lucky, the field names in the objects correspond closely enough that you can write something that uses RTTI and does fieldname alignment to overcome the datatyping inconsistencies to make your life easier.

    The extensions I'm proposing to the Property declarations would make this kind of thing a whole lot simpler and not dependent on naming coincidences. (At least, that's the goal.) And, yes, it can also be used to build streaming interfaces.

    ReplyDelete

Post a Comment