Re: Bikeshedding the gnome-class mini-language



On Tue, 2017-10-24 at 19:03 -0500, Federico Mena Quintero wrote:
On Wed, 2017-10-25 at 00:27 +0200, Sebastian Dröge wrote:

Do you plan to also add support for public fields in the instance
struct?

Nope.  This has made it all to easy to break the ABI in the past, and
all of GTK+ has moved to private structs, anyway.

I realize that public fields may be convenient for "internal" GObjects
that are never exported, though.  Maybe we have different use cases in
mind? (I'm all about libraries with GObject Introspection.)

I don't need support for public fields, I was just curious what your
plans there are :)

How about (public) fields in classes though? Those allow to do some
degree of meta-programming with GObject and we use that in GStreamer
(and GObject also uses that, e.g. properties).

maybe we should have something like

gobject_gen! {
    class Foo {
        ...

        reserve_slots(N);    // to be decremented when needed
    }
}

Much nicer, yes. In GStreamer we just use a
  gpointer _padding[GST_PADDING-X];
field, which is basically the same as your reserve_slots() API
functionality-wise.

private_init() is a mandatory function that gets called during


This should probably take the instance as parameter, or a different
function for hooking into instance_init would be useful. To set-up
any
things that might be necessary for the base-class to properly
initialize everything.

The idea is that private_init() returns a FooPrivate struct, without
ever having the chance to look at uninitialized memory.

Everything in instance_init() is already initialized (to all-zeroes).
But I was more thinking of calling functions of the subclass. E.g. in
GStreamer you usually set up static pads in there.

To set up extra things, we can very well have an init(&self) {} block
or something.  I suppose this ties with the discussion on constructors
below.

Sounds good

Then it should also be possible to impl derive for the struct
manually,
the default derived impl is not always possible/correct :)

Something like

gnome_class! {
    class Foo {
        struct FooPrivate {
            ...
        }

        impl Default for FooPrivate {
            ...
        }
    }
}

?
You know, that may be more Rust-like than the whole private_init()
thing.  Do you like it better?

Yes :)

I'm torn between putting the "impl Blah for Bleh" inside or outside the
class block.  If I were trying to make this look more like an extension
to the language, maybe something like

gnome_class! {
    class Foo: Superclass {
        private = FooPrivate; // similar to associated types, "type Foo = Bar;"

        // should we put the init/constructor/etc here?

        fn init(&self) {
            ...
        }

        fn constructor(&self) {
            // how do we pass in the construct properties comfortably?
        }
    }

    struct FooPrivate {
        ...
    }

    // or #[derive(Default)] above if it works for you
    impl Default for FooPrivate {
        fn default() -> FooPrivate {
            ...
        }
    }

    // this defines the class ABI, basically
    impl Foo {
        pub fn static_method(&self, ...) {
            ...
        }

        virtual fn virtual_method(&self, ...) {
            ...
        }

        fn this_private_method_is_an_implementation_detail(&self) {
            // and is not exported or put in the class slots
        }

        signal some_signal(&self, ...);

        signal with_default_handler(&self, ...) -> Bar {
            // default handler code goes here
        }
    }

    // or just "impl Superclass for Foo"?
    // I kind of like making it obvious that these are overriden things

"impl SuperClass for Foo" would be nicer IMHO. It's the same thing with
implementing traits that have default implementations. Everything you
write in an impl block that is not for your type itself will be
something that is overridden in one way or another.

Note the init()/constructor() inside the "class" block.

+1

How about class_init, base_init and their finalize parts?

Or maybe these should be part of some kind of "impl GObject for Foo"?
They are not really part of Foo but conceptually something that is
overridden/extended.
    
I haven't gotten to the set_prop/get_prop stuff yet.  If you figure out
a nice syntax for them, feel free to add it here :)

impl Foo {
  #[attributes...]
  property some_property: u32 {
    get(&self) -> u32 {
      self.something
    }
    set(&self, value: u32) {
      self.something = value;
    }    
  }
}

Maybe? Not sure about providing the type 3 times, but this way seems
most consistent.

Also, what about dispose/finalize?  In the old code generator,
::finalize() would just Drop the private struct; we don't provide a
good place to put a custom Drop implementation, but with the scheme
above, it is easy to put an "impl Drop for FooPrivate" along with "impl
Default for FooPrivate".

impl Drop for finalize sounds good

Should we have a fake "impl Dispose for Foo" that must have a "fn
dispose(&self)", or add a fake function dispose() along with the
init()/constructor() above?

"impl Dispose" seems more consistent

Could also use a construct {} block for that, it seems like a
reasonable choice. Similarly for class and base construct blocks.

Hmmm, how would you notate those in the scheme above?  Or some other
scheme?

I prefer the functions you use in your example above.

    public MyConstructor (int a, int b, int c) {
        Object (some_construct_only_prop: a, some_other_prop: b);  
        // construct superclass
        this.my_own_prop = c;
    }

I assume this would only take CONSTRUCT properties, and not all of
them, and there can only ever be a single constructor like this?

I don't know if Vala lets you have multiple constructors; it doesn't
sound terribly hard to support them.  Right now I'm a bit more
uncertain on how to get the construct-time properties to a constructor
function :)

You mean how to get them there code-wise? Or how to do the mapping
between parameters and property names?

[signal accumulators]
We use them quite often in GStreamer. You basically need them
whenever
you have a non-trivial return value, or you want to use your return
value to decide if further signal handlers have to be called.
I can give examples if needed.

This would be very helpful - in your gobject-example-rs?

I meant examples where they're used in GStreamer :) Code-wise it's all
simple really.

For example (let's take decodebin):
1) "autoplug-continue" bool accumulator: First TRUE stops signal
emission. This is used for e.g. deciding whether to place a decoder for
a stream or just provide the compressed stream. First TRUE wins. I
think GTK has something similar for various signals, called "inhibit"
iirc?

2) "autoplug-factories" array accumulator: Only the first signal
handler will ever be called and the returned array will be used

3) "autoplug-sort" array accumulator: Stop after the first signal
handler that returned a non-empty array

4) "autoplug-select" enum accumulator: Stop after the first signal
handler that returned a specific enum value

5) "autoplug-query" bool accumulator: Return the boolean || over all
return values

I can find more for you in other places if you're interested.

You could provide them by name in the signal attribute.

signal my_signal(&self, ...)
    where accumulator(i_dont_even_know_the_params)

Syntax ideas appreciated :)

That looks more like Haskell :) In Rust "where" is only used for
putting further bounds on generic type parameters.

I think something like

  #[accumulator(my_accumulator_function)]
  signal my_signal(&self, ...) -> ... { ... }

would be nicer.

What about the class/default handler, how would you handle that?

Just by writing

signal my_signal_with_default_handler(&self, x: i32) {
    println! ("default handler {}": x);
}

That would generate a function and assign its function pointer to the
signal's slot in the class.

Makes sense :)

Sorry to put so much pressure on the syntax right now - it's just that
writing syn parsers that mix a made-up language with syn's own view of
Rust constructs... is not the most pleasant thing, but I'm getting
better at it :)

Don't worry, syntax is the most discussion-friendly part of a new mini-
language :)
See also Wadler's law https://wiki.haskell.org/Wadler%27s_Law

Attachment: signature.asc
Description: This is a digitally signed message part



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]