How to create a more dynamic memberbinder?

Mar 23, 2009 at 10:12 PM
Hi All-

I *think* I need to extend GetMemberBinder(or it's parent DynamicMetaObjectBinder) to duplicate this Common Lisp functionality:
     (defclass test-class nil ((test-field)))
     (setf  *test-inst (make-instance 'test-class ))
     (slot-value *test-instance (funcall (lambda () 'test-field )) )    <-----how do I access a member via a "live" lambda obj, and not just a string?

Do any of you have any suggestions on what I need to do? 

Sorry if the question is vague or if I'm not understanding what the DLR can do by default.

thanks for any assistance
Mike Kohout





Coordinator
Mar 23, 2009 at 10:44 PM

Hi, Mike.  I wasn’t ever a CLOS user, but I’m guessing the semantics is test-field is a slot in the class?  I’m not sure why quoting it in your lambda would get a slot access (but I haven’t used CLOS :-)).  If this is the semantics, then as you map the sexpr to a DLR Expr Tree, you need to do some name binding.  You’ll need to analyze the sexpr enough to realize the ‘test-field is a reference to the class slot.  You can then emit the reference as in a few ways.  You could emit it as a function call that takes the class instance, such as the accessor defclass produces.  You could use a DynamicExpr with a GetMemberBinder, but your DynamicMetaObject for your class inst or your binder would need to recognize the target as a test-class instance, then use CL’s reflection to get the defclass accessor for the slot, then return a DynamicMetaObject with an Expr Tree that calls the accessor passing the class inst.

I realize we may be yelling at each other across a windy abyss :-), but hopefully you can get something form what I’m saying and map it to your ‘test-field semantics?

Bill

From: mwkohout [mailto:notifications@codeplex.com]
Sent: Monday, March 23, 2009 2:13 PM
To: Bill Chiles
Subject: How to create a more dynamic memberbinder? [dlr:51034]

From: mwkohout

Hi All-

I *think* I need to extend GetMemberBinder(or it's parent DynamicMetaObjectBinder) to duplicate this Common Lisp functionality:
(defclass test-class nil ((test-field)))
(setf *test-inst (make-instance 'test-class ))
(slot-value *test-instance (funcall (lambda () 'test-field )) ) <-----how do I access a member via a "live" lambda obj, and not just a string?

Do any of you have any suggestions on what I need to do?

Sorry if the question is vague or if I'm not understanding what the DLR can do by default.

thanks for any assistance
Mike Kohout

Mar 24, 2009 at 3:12 AM
Thanks for throwing your ideas out there Bill, but I think I need it thrown underhand.  DLRwise and .Netwise, I'm still very green(I've got lots of Java, Ruby, and Common Lisp experience though).

The basic syntax of the language I'm playing around with is xml-based.  Imagine this being a call to get a member in my language:
<get-member>
    <target>some-rvalue</target>
    <member-name> some-runtime-var-with-slotname-as-value </member-name>
</get-member>

In my language, I want the slot name resolved at runtime...  According to some value referenced by a runtime variable.

I've included as an attachment an ExpressionGenerator(it's the same thing as a ToyExpression-in fact, this is just a copy of ToyScript.Parser.Ast.Member with some additions ).  It's little more than a sketch, but what it should do is:
1) Look onto the scope stack and find the target.
2) Compile the value of member name into a delegate
3) hopefully, pass the delegate to the binder...or something
4) this new, magical binder will then cause runtime member lookups to work :-).

Does this make it a little less windy, or the abyss a little less infinite?  :-)

thanks
Mike

On Mon, Mar 23, 2009 at 4:45 PM, billchi <notifications@codeplex.com> wrote:
>
> From: billchi
>
> Hi, Mike.  I wasn’t ever a CLOS user, but I’m guessing the semantics is test-field is a slot in the class?  I’m not sure why quoting it in your lambda would get a slot access (but I haven’t used CLOS :-)).  If this is the semantics, then as you map the sexpr to a DLR Expr Tree, you need to do some name binding.  You’ll need to analyze the sexpr enough to realize the ‘test-field is a reference to the class slot.  You can then emit the reference as in a few ways.  You could emit it as a function call that takes the class instance, such as the accessor defclass produces.  You could use a DynamicExpr with a GetMemberBinder, but your DynamicMetaObject for your class inst or your binder would need to recognize the target as a test-class instance, then use CL’s reflection to get the defclass accessor for the slot, then return a DynamicMetaObject with an Expr Tree that calls the accessor passing the class inst.
>
> I realize we may be yelling at each other across a windy abyss :-), but hopefully you can get something form what I’m saying and map it to your ‘test-field semantics?
>
> Bill
>
> From: mwkohout [mailto:[email removed]]
> Sent: Monday, March 23, 2009 2:13 PM
> To: Bill Chiles
> Subject: How to create a more dynamic memberbinder? [dlr:51034]
>
> From: mwkohout
>
> Hi All-
>
> I *think* I need to extend GetMemberBinder(or it's parent DynamicMetaObjectBinder) to duplicate this Common Lisp functionality:
> (defclass test-class nil ((test-field)))
> (setf *test-inst (make-instance 'test-class ))
> (slot-value *test-instance (funcall (lambda () 'test-field )) ) <-----how do I access a member via a "live" lambda obj, and not just a string?
>
> Do any of you have any suggestions on what I need to do?
>
> Sorry if the question is vague or if I'm not understanding what the DLR can do by default.
>
> thanks for any assistance
> Mike Kohout
>
> Read the full discussion online.
>
> To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)
>
> To start a new discussion for this project, email dlr@discussions.codeplex.com
>
> You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.
>
> Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com
Mar 24, 2009 at 3:15 AM
Doh!  Codeplex doesn't handle attachments.  Here it is:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Xml.Linq;

using Microsoft.Linq.Expressions;

using XtraDLRge.Parser;

using XtraDLRge.Binders;

namespace XtraDLRge.ExpressionGenerators

{

    //I Don't think that the generated expression is setf-able.....

    public class Member:ExpressionGenerator

    {

        XElement _e;

        public Member(XElement e):base(new Microsoft.Scripting.SourceSpan()) {

            _e = e;

        }



        public override Microsoft.Linq.Expressions.Expression Generate(XtraDLRge.Parser.ScopeStack ss)

        {


            Expression target = Eval(_e.Element("target")).Generate(ss);

            //the name of the expression is a dynamic thing...so we've got to evaluate it somewhere I think

            LambdaExpression memberName = Expression.Lambda(Eval(_e.Element("member-name")).Generate(ss));

            Delegate memberNameDelegate = memberName.Compile();

            return Expression.Dynamic(

                    new XtraDLRGetMemberBinder(memberNameDelegate),

                    typeof(object),

                    target

                );

        }

    }

}

Coordinator
Mar 24, 2009 at 3:32 AM

Are you just saying that you want to look up a member based upon a name that’s dynamically determined?

The simple way to do this is just to have a runtime helper which has a hash table from name -> CallSite<Func<CallSite, object, object>> and create new call sites for each name you see.

The optimal way may be to create a call site which inherits from DynamicMetaObjectBinder and is specialized for just this purpose.  If you assume the incoming arguments to this call site are always:

object self, string name

You could then produce a DynamicMetaObject like:

DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args) {

string name = (string)args[0].Value;        // assuming always a string…

return new DynamicMetaObject(

                                Expression.Dynamic(                      // create the new getmember

                                                new MyGetMemberBinder(name),

                                                typeof(object),

                                                target

                                ),

                                BindingRestrictions.GetExpressionRestriction(   // always valid for instances of this name

                                                Expression.Equal(

                                                                args[0],

                                                                Expression.Constant(name)

                                                )

                                )

);

}

So here we’re just creating a new rule which is per-name coming in instead of per-type or anything like that.  Then we dispatch to another call site which does the actual get member on the specific name.

Mar 24, 2009 at 5:27 AM


Awesome!  Thanks for the answer.  This is the first time I've had to deal with a binder so it was throwing me for a loop.
Coordinator
Mar 24, 2009 at 3:13 PM

It may not help, but here’s some working code in python that builds a runtime helper class that looks names up on IDynObjs that are contained in variables (and does so case-INsensitively like CL would :-)):

import System.Linq.Expressions as Exprs

from System.Dynamic import (ExpandoObject, InvokeBinder, DynamicMetaObject,

                           GetMemberBinder, SetMemberBinder, CallInfo,

                           BindingRestrictions, IDynamicMetaObjectProvider,

                           InvokeMemberBinder)

from System.Runtime.CompilerServices import CallSite

class DynamicObjectHelpers (object):

   Sentinel = object()

  

   @staticmethod

   def HasMember (dynObj, name):

       if not isinstance(dynObj, IDynamicMetaObjectProvider):

           raise Exception("DynamicObjectHelpers only works on IDOs for now.")

       return (DynamicObjectHelpers.GetMember(dynObj, name) !=

              DynamicObjectHelpers.Sentinel)

    @staticmethod

   def GetMember (dynObj, name):

       if not isinstance(dynObj, IDynamicMetaObjectProvider):

           raise Exception("DynamicObjectHelpers only works on IDOs for now.")

       site = CallSite[Func[CallSite, object, object]].Create(

                  DOHelpersGetMemberBinder(name))

       return site.Target(site, dynObj)

   @staticmethod

   def GetMemberNames (dynObj):

       if not isinstance(dynObj, IDynamicMetaObjectProvider):

           raise Exception("DynamicObjectHelpers only works on IDOs for now.")

       return (dynObj.GetMetaObject(Exprs.Expression.Parameter(object, "bogus"))

                      .GetDynamicMemberNames())

   @staticmethod

   def SetMember(dynObj, name, value):

       if not isinstance(dynObj, IDynamicMetaObjectProvider):

           raise Exception("DynamicObjectHelpers only works on IDOs for now.")

        site = CallSite[Action[CallSite, object, object]].Create(

                 DOHelpersSetMemberBinder(name))

       site.Target(site, dynObj, value)

class DOHelpersGetMemberBinder (GetMemberBinder):

    def __new__ (cls, name):

       return GetMemberBinder.__new__(cls, name, True)

   def FallbackGetMember(self, targetMO, errorSuggestionMO):

       ## Don't add my own type restriction, target adds them.

        return DynamicMetaObject(

                 Exprs.Expression.Constant(DynamicObjectHelpers.Sentinel),

                 targetMO.Restrictions)

From: mwkohout [mailto:notifications@codeplex.com]
Sent: Monday, March 23, 2009 9:28 PM
To: Bill Chiles
Subject: Re: How to create a more dynamic memberbinder? [dlr:51034]

From: mwkohout



Awesome! Thanks for the answer. This is the first time I've had to deal with a binder so it was throwing me for a loop.

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com