November 13, 2013
On 2013-11-12 23:45, Andrei Alexandrescu wrote:

> It's not quite trivial - somewhere there has to be a map and
> registration and lookup and whatnot. I don't see it why it's unbecoming
> for such functionality to be part of the standard library. I would
> agree, however, that it's a judgment call whether it should be wired in
> or provided on demand.

From a (de)serialization point of view, I like that it's not necessary to register every class that should be (de)serialized.

-- 
/Jacob Carlborg
November 13, 2013
On 2013-11-13 01:36, deadalnix wrote:

> The serialization is a good example. You'll have to note that if the
> code has been able to serialize the data, it can generate at compile
> time the necessary scafolding to deserialize it.

No, not necessarily. If you serialize an object through a base class reference you currently need to register that with the serializer. The static type information is lost and D doesn't provide enough runtime reflection to get the values of instance variables of an object at runtime.

-- 
/Jacob Carlborg
November 13, 2013
On 2013-11-13 03:15, Andrei Alexandrescu wrote:

> Wait, doesn't Object.factory call the default constructor of the created
> object?

Yes, but you use ClassInfo.find and _d_newclass, which is exactly the same thing, except it won't call the constructor. Object.factory uses these functions internally.

-- 
/Jacob Carlborg
November 13, 2013
On 2013-11-13 05:07, Andrei Alexandrescu wrote:

> Then how do you figure doing this:
>
> class Streamable { ... }
> class Foo : Streamable { ... }
> class Bar : Streamable { ... }
> string className = stream.readln();
> Streamable obj = ...;
>
> How do you create obj from className, when className could be either
> "Foo" or "Bar"? In the general case there could be any number of
> classes, in different modules.

This requires Object.factory (or equivalent) and that all subclasses have been registered as well. Since you don't know which subclass "className" represents you're forced to deserialize by doing this:

Streamable obj = deserialize!(Streamable)(className);

-- 
/Jacob Carlborg
November 13, 2013
On 11/13/13 12:55 AM, Jacob Carlborg wrote:
> On 2013-11-13 05:07, Andrei Alexandrescu wrote:
>
>> Then how do you figure doing this:
>>
>> class Streamable { ... }
>> class Foo : Streamable { ... }
>> class Bar : Streamable { ... }
>> string className = stream.readln();
>> Streamable obj = ...;
>>
>> How do you create obj from className, when className could be either
>> "Foo" or "Bar"? In the general case there could be any number of
>> classes, in different modules.
>
> This requires Object.factory (or equivalent) and that all subclasses
> have been registered as well.

With Object.factory that's taken care of already.

> Since you don't know which subclass
> "className" represents you're forced to deserialize by doing this:
>
> Streamable obj = deserialize!(Streamable)(className);

Again there is a confusion here. The idea was to create an object with the dynamic type Foo if the class name was "Foo" and an object with the dynamic type Bar if the class name was "Bar". You are glossing over the gist of it all with a function call. Are you sure we are talking about the same thing?


Andrei

November 13, 2013
On 2013-11-13 10:27, Andrei Alexandrescu wrote:

> With Object.factory that's taken care of already.

No, you need to register the subclasses as well. The static type information is lost for the subclasses.

> Again there is a confusion here. The idea was to create an object with
> the dynamic type Foo if the class name was "Foo" and an object with the
> dynamic type Bar if the class name was "Bar". You are glossing over the
> gist of it all with a function call. Are you sure we are talking about
> the same thing?

Here is an excerpt of my serialization library, with the relevant code for this discussion:

void function (in Object) [ClassInfo] registeredTypes;

void register (T : Object) ()
{
    registeredTypes[T.classinfo] = &downcastSerialize!(T)
}

void downcastDeserialize (U : Object) (in Object value)
{
    alias Unqual!(U) T;
    auto casted = cast(T) value;

    assert(casted);
    assert(casted.classinfo is T.classinfo);

    deserializeHelper(casted);
}

void deserializeHelper (T) (T value) { ... }

bool isBaseClass (T) (T value)
{
    return value.classinfo !is T.classinfo;
}

T deserialize (T) (string name)
{
    T t;

    if (auto classInfo = ClassInfo.find(name))
    {
         auto value = cast(T) _d_alloc(classInfo);

        if (isBaseClass(value))
        {
            if (auto deserializer = value.classinfo in registeredTypes)
                (*serializer)(value);

            else
                throw new Exception("Unregistered subclass");
        }

        else
            deserializeHelper(value);

        return value;
    }

    else
    {
        throw new Exception("The class " ~ name ~ " couldn't be found");
        return null;
    }
}

For the full code see: https://github.com/jacob-carlborg/orange/blob/master/orange/serialization/Serializer.d

-- 
/Jacob Carlborg
November 13, 2013
On 11/13/13 2:46 AM, Jacob Carlborg wrote:
> On 2013-11-13 10:27, Andrei Alexandrescu wrote:
>
>> With Object.factory that's taken care of already.
>
> No, you need to register the subclasses as well. The static type
> information is lost for the subclasses.

We are definitely talking about different things.

>      if (auto classInfo = ClassInfo.find(name))

You didn't have to register for find to work, which was my point.


Andrei


November 13, 2013
On 11/13/13 2:46 AM, Jacob Carlborg wrote:
> Here is an excerpt of my serialization library, with the relevant code
> for this discussion:
[snip]

To drive the point home: I looked once more over the code and I think it's too complicated for what it does. It could be improved as follows.

The key to simplification is using

Object create() const;

from ClassInfo (aka Typeinfo_Class) in addition to

static const(TypeInfo_Class) find(in char[] classname);

in the same type. The procedure would go as follows:

1. Retrieve the ClassInfo object for the class name.

2. Create a default-constructed object by calling create() against the object obtained at (1).

3. Cast that Object down to IDeserializable, which is an interface type that has methods such as deserialize(Archive).

4. If the cast failed, throw an exception - the object was not designed to be deserialized.

5. Otherwise, call deserialize against that object, passing it the archive.

Voila! No need for runtime registration - all user code has to do is implement the appropriate interface.

I hope this both clarifies my point about factory being a sensible feature, and helps you improve your library.


Andrei

November 13, 2013
13-Nov-2013 13:27, Andrei Alexandrescu пишет:
> On 11/13/13 12:55 AM, Jacob Carlborg wrote:
>> On 2013-11-13 05:07, Andrei Alexandrescu wrote:
>>
>>> Then how do you figure doing this:
>>>
>>> class Streamable { ... }
>>> class Foo : Streamable { ... }
>>> class Bar : Streamable { ... }
>>> string className = stream.readln();
>>> Streamable obj = ...;
>>>
>>> How do you create obj from className, when className could be either
>>> "Foo" or "Bar"? In the general case there could be any number of
>>> classes, in different modules.
>>
>> This requires Object.factory (or equivalent) and that all subclasses
>> have been registered as well.
>
> With Object.factory that's taken care of already.

I have to chime in.

Serialization != calling default constructor by name.
You have to work out fields somehow - Object.factory doesn't help with that. What serialization needs is some hook - be it delegete or whatnot:

Object deserialize(SomeStreamingAbstraction here);

Simply snatching a name is not serialization. Not to mention that far too many protocols are not text.

Keeping built-in map of class-name --> "create via default constructor"
is an awful example of ONE SIZE FITS ALL. It doesn't!

Simply on the ground of "solves problem in too narrow scope for a hefty coast to the user" I'd drop it.


-- 
Dmitry Olshansky
November 13, 2013
On 11/13/13 10:41 AM, Dmitry Olshansky wrote:
> 13-Nov-2013 13:27, Andrei Alexandrescu пишет:
>> On 11/13/13 12:55 AM, Jacob Carlborg wrote:
>>> On 2013-11-13 05:07, Andrei Alexandrescu wrote:
>>>
>>>> Then how do you figure doing this:
>>>>
>>>> class Streamable { ... }
>>>> class Foo : Streamable { ... }
>>>> class Bar : Streamable { ... }
>>>> string className = stream.readln();
>>>> Streamable obj = ...;
>>>>
>>>> How do you create obj from className, when className could be either
>>>> "Foo" or "Bar"? In the general case there could be any number of
>>>> classes, in different modules.
>>>
>>> This requires Object.factory (or equivalent) and that all subclasses
>>> have been registered as well.
>>
>> With Object.factory that's taken care of already.
>
> I have to chime in.
>
> Serialization != calling default constructor by name.

Of course not. It does give you access to an object with the correct dynamic type, and interfaces and virtual calls take care of the rest.

> You have to work out fields somehow - Object.factory doesn't help with
> that. What serialization needs is some hook - be it delegete or whatnot:
>
> Object deserialize(SomeStreamingAbstraction here);

Or a method.

> Simply snatching a name is not serialization. Not to mention that far
> too many protocols are not text.

Method.

> Keeping built-in map of class-name --> "create via default constructor"
> is an awful example of ONE SIZE FITS ALL. It doesn't!

It is: it achieves virtual construction, the only missing piece in a classic OOP scheme. Once you have the object, the rest is smooth sailing.

> Simply on the ground of "solves problem in too narrow scope for a hefty
> coast to the user" I'd drop it.

It's good. Let's keep it.



Andrei