djinni icon indicating copy to clipboard operation
djinni copied to clipboard

Readonly and final

Open marzimdrums opened this issue 9 years ago • 11 comments

Our use case is something like settings: Let's say we have a settings object with settings. We get it from SQLITE through C++ to Android/iOS where we then have a ObjC/Java Settings object.

Let's say we want to change one setting. Like "amountOfWater". Now as the objects all have final/readonly properties, our android/iOS devs need to make a new object and basically copy everything but one value and hand it back to the C++ function (somethingLike: updateSettings(settings)).

Is there some workaround or idea that our mobile developers can just change that single value and hand the object back? What is the exact reason to make the properties final/readonly?

marzimdrums avatar Sep 18 '15 08:09 marzimdrums

Define getters/sertters per setting at the IDL level?

MOZGIII avatar Sep 22 '15 06:09 MOZGIII

As far as I see I could do that with an interface which leads to lots of boilerplate code. And with records this is not possible, or do I miss something?

marzimdrums avatar Sep 22 '15 06:09 marzimdrums

As I understood from the C++ 2014 talk, the actual data is marshalled and copied on each call anyways. Probably they have some optimization in mind (or already implemented) that would require objects to be constant on the boundary.

But it seems like the only way to update an object if you only access it as a copy and never by reference is to just pass it all. Or pass a "patch".

In your case, you may try introducing property system, where you have an API like valuetype property(string name) and void setProperty(string name, valuetype newValue) to reduce the bilerplate. However it reduces type safety of the resulting code.

That seems to be sematically equal to calculating diff of models across the boundaries, provided that only one property can be different at a time...

MOZGIII avatar Sep 22 '15 06:09 MOZGIII

Notice I've only just watched the talk (that was recorded approx. 1 year ago), and not sure myself if I understand what's going on today correctly.

MOZGIII avatar Sep 22 '15 06:09 MOZGIII

In the current implementation, the only reason for records to be immutable is to avoid confusion. If they were mutable, it would be easy for developers to assume they could change a record in one language, and see the result in another language, which won't work because records are always marshaled by copy.

This limitation does also leave room for future optimization where if the same record is marshalled multiple times we could cache the first copy for reuse. That optimization wouldn't be safe if records were mutable.

I think you can accomplish the use case you want using an extended record (record +c) to define your own extra methods on the record. Such a method could include method to make a new record with all the same fields but one changed field passed as an argument. That's not maximally efficient, but will let you write the necessary code only once, and provide a simple API. You could also make a mutable "builder" type with the same fields as the record. A generator for such builders is something I could imagine being useful as a Djinni feature if you wanted to implement it.

artwyman avatar Jan 06 '16 22:01 artwyman

I think there's a deeper distinction between records and interfaces than just that records are copied. That choice was driven by the fact that in C++, records and primitives are value types: they're passed by value (for raw numbers and enums) or by const reference, their lifetime can be anything (not necessarily controlled by shared_ptr like an interface), and they're fungible; one std::string containing "hello" is no different than another. Caching different instances of a record wouldn't be possible, since the Djinni translation code doesn't have any visibility into the lifetime of the record object. If we receive a const MyRecord &, it's not safe to keep its address around, since we don't know how long the underlying instance will last.

It seems like there's room for a new concept in Djinni, though I'm not sure what it would be called or whether the utility would outweigh the implementation/maintenance costs: a mutable data-carrying object supporting cross-language access and ownership. Imagine a large interface +c +j +o full of getters and setters for many different fields. Djinni could additionally generate a simple implementation of that interface in some or each language (constructor, private members, get/set method implementations).

So we'd have something like:

settings = mutablerecord { # need a better name
    foo: string;
    bar: i64;
    # ... much more
}

Which would be equivalent to:

settings = interface +c +j +o {
    get_foo(): string;
    set_foo(foo: string);
    get_bar(): i64;
    set_bar(bar: i64);
    # ...
}

But then Djinni could additionally generate:

class SettingsImpl : public Settings {
public:
    SettingsImpl(const std::string & foo, int64_t bar) : m_foo(foo), m_bar(bar) {}
    std::string get_foo() const override { return m_foo; }
    void set_foo(const std::string & foo) override { m_foo = foo; }
    int64_t get_bar() const override { return m_bar; }
    void set_bar(int64_t bar) { m_bar = bar; }
    // ...
private:
    std::string m_foo;
    int64_t m_bar;
    // ...
};

... As well as equivalents in Java and ObjC. That way the 'record' could natively exist in any of the languages, and be accessed from any other.

Again, I'm not sure if this is worth the extra overhead, but it seems like it might be what people are asking for.

j4cbo avatar Jan 10 '16 00:01 j4cbo

So if it doesn't really differ from an interface +c +o +j with the generated setters/getters then isn't the actual abstraction we're looking for generates setters/getters since we already have the other half of the equation? Wouldn't it be easier to just provide some kind of property field type where Djinni generates setters/getters and backing fields?

How exactly the setters/getters are generated (virtual or not, backing fields or not etc.) is open (and I'd like some support for data binding if possible) but I think a property-like abstraction is what people are really looking for, not an entire new concept with more idioms and restrictions.

mknejp avatar Jan 10 '16 09:01 mknejp

Ah yeah, I hadn't thought about this in relation to the property concept. Seems like you're right - this could be the same thing. On Sun, Jan 10, 2016 at 1:12 AM Miro Knejp [email protected] wrote:

So if it doesn't really differ from an interface +c +o +j with the generated setters/getters then isn't the actual abstraction we're looking for generates setters/getters since we already have the other half of the equation? Wouldn't it be easier to just provide some kind of property field type where Djinni generates setters/getters and backing fields?

How exactly the setters/getters are generated (virtual or not, backing fields or not etc.) is open (and I'd like some support for data binding if possible) but I think a property-like abstraction is what people are really looking for, not an entire new concept with more idioms and restrictions.

— Reply to this email directly or view it on GitHub https://github.com/dropbox/djinni/issues/142#issuecomment-170328841.

j4cbo avatar Jan 10 '16 17:01 j4cbo

+1 for properties on interfaces as a good way of representing of the needs here. Properties turning into method declarations for implementers to fill in themselves would be pretty simple. Filling in the implementation automatically would get tricker since it would require a base class with data (not a protocol in ObjC). There's also the question of where the actual data lives when you create a new object, which has an effect on efficiency of getters/setters. I could imagine always locating the data in the same language, hard-coded or chosen from IDL. Alternately, you could create an impl in whichever language creates the object in the first place, then marshal to other languages as necessary.

I think builders for creating records would meet a different set of needs (and I'm a fan of immutable objects in general). In an ideal world, maybe you'd have all of the above linked together. For each record there'd be a corresponding builder represented as an interface with properties.

With respect to caching: Caching by object identity couldn't work for all cases since it wouldn't be preserved by moves. It could help with arguments, though, if the marshalling code were changed to marshal into a heap object to save in the cache, then pass along a reference to it. The alternative would be to cache by contents, using hash codes and equality comparision in the cache, which would have the cost of a hash/compare, but still save a copy. That alternative wouldn't be messed up by mutability, though, so it wasn't the one I had in mind in my comment above.

artwyman avatar Jan 12 '16 06:01 artwyman

Given that records are immutable value types, is there any reason why struct members of records in C++ are not const?

webmaster128 avatar Jun 30 '17 17:06 webmaster128

Making them const would make it impossible for records to be moved, making them expensive to pass around as value types, particularly in return values. @webmaster128 for interactive Q&A I'd recommend joining the Slack community mentioned at the end of the Djinni README.md file.

artwyman avatar Jun 30 '17 19:06 artwyman