Jump to page: 1 2
Thread overview
Events in D
Aug 29, 2017
bitwise
Aug 29, 2017
Andrea Fontana
Aug 29, 2017
bitwise
Aug 29, 2017
Vadim Lopatin
Aug 29, 2017
bitwise
Aug 29, 2017
Jonathan Marler
Aug 29, 2017
bitwise
Aug 29, 2017
bitwise
Aug 29, 2017
bitwise
Aug 29, 2017
kinke
Aug 29, 2017
bitwise
Aug 30, 2017
Heromyth
Aug 30, 2017
Kagamin
Aug 30, 2017
bitwise
Aug 31, 2017
kinke
Aug 31, 2017
bitwise
Aug 31, 2017
bitwise
August 29, 2017
I needed some C# style events, so I rolled my own. Long story short, the result was unsatisfactory.

Library based events are inadequate for basically the same reasons as library based properties (often suggested/attempted in C++). The problem is that the properties/events don't have access to the fields or methods of the containing object, and as such, incur the cost of an extra pointer per event/property, or worse, a delegate if custom behavior per event is needed, in order to provide that access. One obvious example would be synchronized properties/events.

Anyways, I threw together some code while thinking about what an event may look like in D:

struct Foo
{
    List!(void function()) callbacks;

    @event void onEvent(string op, Args...)(Args args)
    {
        static if(op == "+")
        {
            callbacks.add(args[0]);
        }
        else static if(op == "-")
        {
            callbacks.remove(args[0])
        }
        else static if(op == "()")
        {
            foreach(cb; callbacks)
                cb(args);
        }
    }

    // or..

    @event {
        void onEvent(string op, Args...)(Args args)
            if(op == "+" && Args.length == 1 && isSomeFunction(Args[0]))
        {
            callbacks.add(args[0]);
        }

        void onEvent(string op, Args...)(Args args)
            if(op == "-" && Args.length == 1 && isSomeFunction(Args[0]))
        {
            callbacks.remove(args[0]);
        }

        void onEvent(string op, Args...)(Args args)
            if(op == "()" && __traits(compiles, { callbacks[0](args); })
        {
            foreach(cb; callbacks)
                cb(args);
        }

        // this could work in the example above
        // if events just always returned an int
        bool onEvent(string op, Args...)(Args args)
            if(op == "!!" && Args.length == 0)
        {
            return !callbacks.empty;
        }
    }
}

void baz(int n) {
    writeln(n);
}

so usage like this:

`
Foo foo;
foo.onEvent += (int n) => writeln(n);
foo.onEvent += &baz;
foo.onEvent -= &baz;

if(foo.onEvent)
    foo.onEvent(1);
`

becomes this:

`
Foo foo;
foo.onEvent!"+"(() => writeln("bar"));
foo.onEvent!"+"(&baz);
foo.onEvent!"-"(&baz);

if(foo.onEvent!"!!"())
     foo.onEvent!"()"(1);
`

and outputs this:

1

August 29, 2017
On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
> [...]
>         static if(op == "+")
> [...]

Maybe "~"? Usually "+" means "sum" not "add"/"concat".
Anyway I remember that something similar was used on DFL [1]

[1] http://www.dprogramming.com/dfl.php



August 29, 2017
On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
> I needed some C# style events, so I rolled my own. Long story short, the result was unsatisfactory.
>
> Library based events are inadequate for basically the same reasons as library based properties (often suggested/attempted in C++). The problem is that the properties/events don't have access to the fields or methods of the containing object, and as such, incur the cost of an extra pointer per event/property, or worse, a delegate if custom behavior per event is needed, in order to provide that access. One obvious example would be synchronized properties/events.
>
> Anyways, I threw together some code while thinking about what an event may look like in D:

DlangUI includes signal/slot event implementation.

https://github.com/buggins/dlangui/blob/master/src/dlangui/core/signals.d


August 29, 2017
On Tuesday, 29 August 2017 at 08:05:48 UTC, Andrea Fontana wrote:
> On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
>> [...]
>>         static if(op == "+")
>> [...]
>
> Maybe "~"? Usually "+" means "sum" not "add"/"concat".
> Anyway I remember that something similar was used on DFL [1]
>
> [1] http://www.dprogramming.com/dfl.php

True that "~" would be more D-like, but then "-" wouldn't make sense.

Also, DFL must be using a library implementation, which means it's limited as I've described above.
August 29, 2017
On Tuesday, 29 August 2017 at 11:26:36 UTC, Vadim Lopatin wrote:
> 
> DlangUI includes signal/slot event implementation.
>
> https://github.com/buggins/dlangui/blob/master/src/dlangui/core/signals.d

Again, this is a library implementation which suffers from the problems described in the original post.
August 29, 2017
On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
> I needed some C# style events, so I rolled my own. Long story short, the result was unsatisfactory.
>
> Library based events are inadequate for basically the same reasons as library based properties (often suggested/attempted in C++). The problem is that the properties/events don't have access to the fields or methods of the containing object, and as such, incur the cost of an extra pointer per event/property, or worse, a delegate if custom behavior per event is needed, in order to provide that access.

I'm confused, C# has the same problem with events.  They are delegates which under the hood have 2 pointers, a pointer to the method and a pointer to an instance of the object.  How would that be different than if you used delegates in your D library?


August 29, 2017
On Tuesday, 29 August 2017 at 16:25:33 UTC, Jonathan Marler wrote:
> On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
>> [...]
>
> I'm confused, C# has the same problem with events.  They are delegates which under the hood have 2 pointers, a pointer to the method and a pointer to an instance of the object.  How would that be different than if you used delegates in your D library?

You're right that a D event would also incur the same cost for adding a delegate to an event. However, there are additional cost and problems that come with having the event in a self-contained struct that sits in some host object.

1) additional memory cost of a pointer to the event's host object if access to a shared mutex, lock, or anything is needed (even if no events are attached).

2) additional memory cost of one or more delegates to methods of the host object if any special logic is needed to update the host object's state in some way when an event is added or removed (even if no events are attached). Consider how bad this could get when an object needs 4-5, or even more events.

3) inflexibility. It's impossible to satisfy everything that one may need with a library implementation, and any attempt at doing so would result in an extremely bloated, and still inadequate abstraction. For example, @nogc/@safe/etc attributes, choice of custom internal container/allocator, signature of callbacks to host objects, and I'm sure there's more.

The solution I presented is very simple and easily accounts for all of the above stated problems - No memory overhead, direct access to host object's members, choice of any attributes or event storage you want, straight-forward syntax.


August 29, 2017
On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
> [...]

I think I should clarify for anyone with limited C# experience, that I'm talking about the custom-event syntax, not the regular one-liner syntax:

class MyClass
{
    Object myLock;
    EventHandler _completed;

    public event EventHandler Completed
    {
        add {
            lock (myLock) {
                _completed = (EventHandler)Delegate.Combine(_completed, value);
                // update some other state
            }
        }
        remove {
            lock(myLock) {
                _completed = (EventHandler)Delegate.Remove(_completed, value);
                // update some other state
            }
        }
    }

    void RaiseCompleted()
    {
        EventHandler c = null;

        lock(myLock) {
            c = _completed;
        }

        if(c != null)
            c();
    }
}


August 29, 2017
On Tuesday, 29 August 2017 at 16:25:33 UTC, Jonathan Marler wrote:
> [...]
>

While responding to your question, I provided an example for needing access to the host's data members (Mutex), but failed to provide an example of needing an extra delegate-to-host for an event. I just hit that case though, and it's timers/animations.

For any app where battery life is a concern, you can't just spin for no reason. So when you add a handler to a timer/animation event, you have to kick off whatever timer handles the animation if it's not running. Likewise, you have to stop it when all events have been removed.
August 29, 2017
On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
> I needed some C# style events, so I rolled my own. Long story short, the result was unsatisfactory.
>
> [...]

> Anyways, I threw together some code while thinking about what an event may look like in D:
>
> [...]

I like the C# event syntax too and came up with the following D analogon, just to prove that a primitive library-based solution in D is doable in 35 lines and can offer as much comfort as C# here.

struct Event(Args)
{
    alias CB = void delegate(Args);
    CB[] callbacks;

    void opOpAssign(string op)(CB handler)
        if (op == "+" || op == "-")
    {
        static if (op == "+")
            callbacks ~= handler;
        else
        {
            import std.algorithm.mutation : remove;
            callbacks = callbacks.remove!(x => x == handler);
        }
    }

    void opOpAssign(string op)(void function(Args) handler)
        if (op == "+" || op == "-")
    {
        import std.functional : toDelegate;
        opOpAssign!op(toDelegate(handler));
    }

    void opCall(Args args)
    {
        foreach (cb; callbacks)
            cb(args);
    }

    bool opCast(T)()
        if (is(T == bool))
    {
        return callbacks.length != 0;
    }
}

The following test code prints the expected output:

struct S
{
    int a;
    void handler(int arg)
    {
        printf("S.handler: this.a = %d, arg = %d\n", a, arg);
    }
}

void func(int arg) { printf("func: arg = %d\n", arg); }

void main()
{
    Event!int onChanged;
    auto s = S(666);

    assert(!onChanged);

    onChanged += (int arg) { printf("lambda: arg = %d\n", arg); };
    onChanged += &func;
    onChanged += &s.handler;
    assert(onChanged);
    onChanged(1);

    onChanged -= &s.handler;
    onChanged(2);

    onChanged -= &func;
    onChanged(3);
}
« First   ‹ Prev
1 2