Announcement

Collapse
No announcement yet.

[OPEN] [#958] Make generic method's type parameters part of function signature

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    [OPEN] [#958] Make generic method's type parameters part of function signature

    In the React bindings, there are some helper functions for React Stores to deal with messages that come from the Dispatcher (an implementation of which is included in the bindings NuGet package) - each time a message that the Dispatcher emits is received by a Store, its type is checked and some work is done based upon its type.

    The codes looks a little bit like the following (greatly simplified for example purposes) -

    _dispatcher.Register(message =>
    {
        message
            .If<SetName>(action => Console.WriteLine("Name: " + action.Name))
            .If<SetValue>(action => Console.WriteLine("Value: " + action.Value));
    });
    The "If" method is a static generic method:

    public static class MessageExtensions
    {
        public static IMessage If<T>(this IMessage source, Action<T> work) where T : IMessage
        {
            if (source is T)
                work((T)source);
            return source;
        }
    }
    When I build this with the Issue 921 preview build, the two lambdas are lifted into named functions, which is great, so I get JavaScript functions:

    $f1: function (action) {
        console.log("Name: " + action.getName());
    },
    $f2: function (action) {
        console.log("Value: " + action.getValue());
    }
    However.. there is more use of anonymous functions in the JavaScript implementation of the generic function "If" -

    $if: function (T) {
        return Bridge.fn.bind(this, function (source, work) {
            if (Bridge.is(source, T)) {
                work(Bridge.cast(source, T));
            }
            return source;
        });
    }
    I was wondering if this could be avoided by changing the way that generic methods are translated, such that the generic type parameters because part of the function signature. So, instead of the above JavaScript, Bridge could produce this:

    $if: function (T, source, work) {
        if (Bridge.is(source, T)) {
            work(Bridge.cast(source, T));
        }
    }
    This is not as important as the lambdas-to-named-functions improvement that you made for Feature Request 1515 but it could still be a nice performance boost.

    In React (well, in Flux architecture apps, actually), each time an interaction occurs from the user, the view fires a message. All stores that are responsible for maintaining data and app state will listen out for these messages. If it's a complicated app then there could be quite a lot of stores, each with multiple calls to "If" to check the types of messages received from the Dispatcher. If the user is hammering away at the keyboard and generating lots of messages (since each key press will emit a message) then this could be a lot of calls to the generic method "If". If you could change how generic methods are represented in the JavaScript (by pulling the type parameters into the signature, rather than each call to a generic method being one call to set the type parameters within a closure and then another call to an anonymous function) then you would halve the number of function calls required and, more importantly, remove the need for all of those allocations of anonymous function values - which would make the browser's garbage collector happier.

    #2
    A complete example, where you could try out what I mean is below (this is just a toy example and doesn't actually have any React stuff in it, it's just to try to illustrate what I mean):

    using System;
    using Bridge.Html5;
    
    namespace BridgeGenericMethodExample
    {
        public class App
        {
            [Ready]
            public static void Main()
            {
                var message = new SetValue("Hi!");
                ProcessMessage(message);
            }
    
            private static void ProcessMessage(IMessage message)
            {
                message
                    .If<SetName>(action => Console.WriteLine("Name: " + action.Name))
                    .If<SetValue>(action => Console.WriteLine("Value: " + action.Value));
            }
        }
    
        public static class MessageExtensions
        {
            public static IMessage If<T>(this IMessage source, Action<T> work) where T : IMessage
            {
                if (source is T)
                    work((T)source);
                return source;
            }
        }
    
        public sealed class SetName : IMessage
        {
            public SetName(string name)
            {
                Name = name;
            }
            public string Name { get; private set; }
        }
    
        public sealed class SetValue : IMessage
        {
            public SetValue(string value)
            {
                Value = value;
            }
            public string Value { get; private set; }
        }
    
        public interface IMessage { }
    }
    this is translated into the following JavaScript:

    /* global Bridge */
    
    (function (globals) {
        "use strict";
    
        Bridge.define('BridgeGenericMethodExample.IMessage');
        
        Bridge.define('BridgeGenericMethodExample.MessageExtensions', {
            statics: {
                $if: function (T) {
                    return Bridge.fn.bind(this, function (source, work) {
                        if (Bridge.is(source, T)) {
                            work(Bridge.cast(source, T));
                        }
                        return source;
                    });
                }
            }
        });
        
        Bridge.define('BridgeGenericMethodExample.SetName', {
            inherits: [BridgeGenericMethodExample.IMessage],
            config: {
                properties: {
                    Name: null
                }
            },
            constructor: function (name) {
                this.setName(name);
            }
        });
        
        Bridge.define('BridgeGenericMethodExample.SetValue', {
            inherits: [BridgeGenericMethodExample.IMessage],
            config: {
                properties: {
                    Value: null
                }
            },
            constructor: function (value) {
                this.setValue(value);
            }
        });
        
        Bridge.define('BridgeGenericMethodExample.App', {
            statics: {
                config: {
                    init: function () {
                        Bridge.ready(this.main);
                    }
                },
                main: function () {
                    var message = new BridgeGenericMethodExample.SetValue("Hi!");
                    Bridge.get(BridgeGenericMethodExample.App).processMessage(message);
                },
                processMessage: function (message) {
                    BridgeGenericMethodExample.MessageExtensions.$if(BridgeGenericMethodExample.SetValue)(BridgeGenericMethodExample.MessageExtensions.$if(BridgeGenericMethodExample.SetName)(message, BridgeGenericMethodExample.App.$f1), BridgeGenericMethodExample.App.$f2);
                },
                $f1: function (action) {
                    console.log("Name: " + action.getName());
                },
                $f2: function (action) {
                    console.log("Value: " + action.getValue());
                }
            }
        });
        
        Bridge.init();
    })(this);
    What would be amazing would be if the JavaScript was more like this:

    /* global Bridge */
    
    (function (globals) {
        "use strict";
    
        Bridge.define('BridgeGenericMethodExample.IMessage');
        
        Bridge.define('BridgeGenericMethodExample.MessageExtensions', {
            statics: {
                $if: function (T, source, work) {
                    if (Bridge.is(source, T)) {
                        work(Bridge.cast(source, T));
                    }
                }
            }
        });
        
        Bridge.define('BridgeGenericMethodExample.SetName', {
            inherits: [BridgeGenericMethodExample.IMessage],
            config: {
                properties: {
                    Name: null
                }
            },
            constructor: function (name) {
                this.setName(name);
            }
        });
        
        Bridge.define('BridgeGenericMethodExample.SetValue', {
            inherits: [BridgeGenericMethodExample.IMessage],
            config: {
                properties: {
                    Value: null
                }
            },
            constructor: function (value) {
                this.setValue(value);
            }
        });
        
        Bridge.define('BridgeGenericMethodExample.App', {
            statics: {
                config: {
                    init: function () {
                        Bridge.ready(this.main);
                    }
                },
                main: function () {
                    var message = new BridgeGenericMethodExample.SetValue("Hi!");
                    Bridge.get(BridgeGenericMethodExample.App).processMessage(message);
                },
                processMessage: function (message) {
                    // Changed the "$if1" call style here into a single call with type parameters as arguments, rather than two calls (one to specify the
                    // type arguments and a subsequent call to provide the function arguments). I've split it onto separate lines simply to try to make
                    // it a little more readable.
                    BridgeGenericMethodExample.MessageExtensions.$if(
                        BridgeGenericMethodExample.SetValue,
                        BridgeGenericMethodExample.MessageExtensions.$if(BridgeGenericMethodExample.SetName, message, BridgeGenericMethodExample.App.$f1),
                        BridgeGenericMethodExample.App.$f2
                    );
                },
                $f1: function (action) {
                    console.log("Name: " + action.getName());
                },
                $f2: function (action) {
                    console.log("Value: " + action.getValue());
                }
            }
        });
        
        Bridge.init();
    })(this);
    This change would also remove the need for the additional Bridge.fn.bind calls within the generic method, since any binding would be dealt with by the caller of the generic method.

    With the proposed change (ie. as may be seen in the example above), there would no longer be any anonymous functions required - much fewer allocations for the GC to worry about!

    Comment


      #3
      What would be amazing would be if the JavaScript was more like this:
      Done. See Branch #921.

      // Old
      processMessage: function (message) {
          BridgeGenericMethodExample.MessageExtensions.$if(
              BridgeGenericMethodExample.SetValue)(BridgeGenericMethodExample.MessageExtensions.$if(
                  BridgeGenericMethodExample.SetName)(message, BridgeGenericMethodExample.App.$f1),
              BridgeGenericMethodExample.App.$f2);
      },
      
      // Proposed
      processMessage: function (message) {
          BridgeGenericMethodExample.MessageExtensions.$if(
              BridgeGenericMethodExample.SetValue,
              BridgeGenericMethodExample.MessageExtensions
                  .$if(BridgeGenericMethodExample.SetName, message, BridgeGenericMethodExample.App.$f1),
              BridgeGenericMethodExample.App.$f2
          );
      },
      
      // New (Branch #921)
      processMessage: function (message) {
          BridgeGenericMethodExample.MessageExtensions.$if(
              BridgeGenericMethodExample.SetValue,
              BridgeGenericMethodExample.MessageExtensions
                  .$if(BridgeGenericMethodExample.SetName, message, BridgeGenericMethodExample.App.$f1),
              BridgeGenericMethodExample.App.$f2);
      },
      Subtle change, big impact. Great suggestion.

      Hope this helps.

      Comment


        #4
        Created an issue - "Make generic method's type parameters part of function signature":
        https://github.com/bridgedotnet/Bridge/issues/958

        Comment


          #5
          The changes suggested in this thread are related to and effected by the changes provided in the following post:

          http://forums.bridge.net/forum/gener...=1597#post1597

          Comment

          Working...
          X