daokoder / dao

Dao Programming Language
http://daoscript.org
Other
199 stars 19 forks source link

Better interface for serializer #399

Open Night-walker opened 9 years ago

Night-walker commented 9 years ago

I think it's time to make something decent-looking out of the serialization facility. The current interface is somewhat 'black-boxy' from the point of modern Dao -- I don't like the use of any when it doesn't really imply 'anything'.

Here's how I envisage it:

type AutoSerializable = bool|enum|int|float|complex|string|list<AutoSerializable>|map<AutoSerializable,AutoSerializable>|tuple<...: AutoSerializable>

interface IndirectSerializable {
    routine decompose() => AutoSerializable 
    static routine compose(invar value: AutoSerializable) => IndirectSerializable 
}

type Serializable = AutoSerializable|IndirectSerializable 

routine serialize(invar value: Serializable) => string
routine deserialize(text: string) => Serializable

AutoSerializable encompasses everything which can be serialized without user hints -- primitive types and containers of primitive types.

IndirectSerializable is something which can be decompose()d into AutoSerializable and then compose()d back. The reason I didn't choose serialize()/deserialize() is backed by the nature of user-defined serialization in Dao: unlike in some other languages, one doesn't define actual mapping to and from textual/binary data, but instead relies on pre-defined serialization of built-in types. There might be more fitting names for this, however.

Unfortunately, it's not possible to bind the type of what decompose() returns to text parameter of compose(). That can't be solved without something akin to Rust's associated types, which would likely be an overkill. The same concerns the value returned by compose() -- logically, it has to be of the same type we implement the interface for. Rust solves this with its special Self type. But those are minor, non-lethal issues.

Finally, Serializable evidently envelops both kinds of types. That's what serialize()/deserialize() operate on.

What would we gain from this change? Improved clarity and control of user-defined serialization. Ability to specify Serializable whenever you require the related compile-time guarantees. There doesn't seem to be any significant drawbacks anyway.

P.S. As usual for Dao, you can notice certain (unintentional) dualism here. Like with types and classes, inheritance and mixing, closures and code sections, hash maps and tree maps. Seems like the language favors this Yin-Yang design, it emerges just about everywhere :)

dumblob commented 9 years ago

Unfortunately, it's not possible to bind the type of what decompose() returns to text parameter of compose(). That can't be solved without something akin to Rust's associated types, which would likely be an overkill. The same concerns the value returned by compose() -- logically, it has to be of the same type we implement the interface for. Rust solves this with its special Self type. But those are minor, non-lethal issues.

If I understood you correctly, I think it very much depends on the context. In case of one-shot serialization, it might be useful to enforce that serialized data can be decomposed only to the original type and not to IndirectSerializable. On the other hand in high-level stuff, we usually require the opposite behavior. Something may be though achieved with syntax macros.

All in all I like the structure you proposed. I'm only asking myself about the length of all the type names. What about (un)marshal (as Go uses) or (de)code or something else (synonyms http://www.thesaurus.com/browse/marshal)?

Night-walker commented 9 years ago

All in all I like the structure you proposed. I'm only asking myself about the length of all the type names. What about (un)marshal (as Go uses) or (de)code or something else (synonyms http://www.thesaurus.com/browse/marshal)?

Whatever. So far, I didn't put much thought into names.

daokoder commented 9 years ago

AutoSerializable encompasses everything which can be serialized without user hints -- primitive types and containers of primitive types.

Sounds like a nice idea.

Unfortunately, it's not possible to bind the type of what decompose() returns to text parameter of compose(). That can't be solved without something akin to Rust's associated types, which would likely be an overkill.

This is indeed an issue, a minor one though.

What would we gain from this change? Improved clarity and control of user-defined serialization. Ability to specify Serializable whenever you require the related compile-time guarantees. There doesn't seem to be any significant drawbacks anyway.

Clearly:)

P.S. As usual for Dao, you can notice certain (unintentional) dualism here. Like with types and classes, inheritance and mixing, closures and code sections, hash maps and tree maps. Seems like the language favors this Yin-Yang design, it emerges just about everywhere :)

Yes, and probably a few more: var and invar, routine decorator and class decorator, asynchronous function (call) and asynchronous class. A few of them are actually intentional (mainly meant for dualism) :)

dumblob commented 5 years ago

Unfortunately, it's not possible to bind the type of what decompose() returns to text parameter of compose(). That can't be solved without something akin to Rust's associated types, which would likely be an overkill. The same concerns the value returned by compose() -- logically, it has to be of the same type we implement the interface for. Rust solves this with its special Self type. But those are minor, non-lethal issues.

We might try to revisit this issue with the use of concrete interfaces. @Night-walker any neat ideas how it could look like?

dumblob commented 5 years ago

One idea would be to make the interface also a streaming interface (i.e. handle an infinite input stream). Code sections might come handy...