Ayende @ Rahien

It's a girl

Challenge: Striving for better syntax

Or, as I like to call them, yes another stupid language limitation. I did some work today on Rhino Mocks, and after being immersed for so long in DSL land, I had a rude awakening when I remembered just how much inflexible the C# language is.

Case in point, I have the following interface:

public interface IDuckPond
{
	Duck GetDuckById(int id);
	Duck GetDuckByNameAndQuack(string name, Quack q);
}

I want to get to a situation where the following will compile successfully:

IDuckPond pond = null;
pond.Stub( x => x.GetDuckById );
pond.Stub( x => x.GetDuckByNameAndQuack );

Any ideas?

Note that unlike my other challenges, I have no idea if this is possible. I am posting this after I got fed up with the limitations of the language.

Comments

Steve
05/27/2008 04:51 PM by
Steve

Not possible unfortunately, you can make it work for the first scenario with a single argument but it will not work in a general case

Ideas? BooLangStudio ; )

Bryan Watts
05/27/2008 05:36 PM by
Bryan Watts

The problem is in the overload resolution - how can the compiler determine which signature to select for a method group?

(Yes, in this case there is only 1 signature, but the general problem is "referring to a method by name").

Really, you have to give more context. The only way for the compiler to select an overload is to provide an actual invocation:

pond.Stub((string name, Quack q) => x.GetDuckByNameAndQuack(name, q));

It has been suggested that Expression<> can be used to do strongly-typed reflection. Perhaps something like:

public static class Reflector

{

public static MethodInfo MethodOf(Expression<Func> expression)

{

...Inspect expression body...

}

public static MethodInfo MethodOf<T, TResult>(Expression<Func<T, TResult>> expression)

{

...Inspect expression body...

}

public static MethodInfo MethodOf<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> expression)

{

...Inspect expression body...

}

...And so on for other Func<> types...

...MemberOf, PropertyOf, and FieldOf would also be useful...

}

used like:

pond.Stub(Reflector.MethodOf(

(string name, Quack q) => x.GetDuckByNameAndQuack(name, q)));

Ayende Rahien
05/27/2008 05:44 PM by
Ayende Rahien

Bryan,

Yes, I suspected that this is the case.

As for your suggestion, take a look here:

http://www.ayende.com/Blog/archive/2005/10/29/8176.aspx

Bryan Watts
05/27/2008 06:04 PM by
Bryan Watts

Well there you go. I guess my Reflector class adds value because the type parameters can be inferred; you don't have to specify the return type.

But yes, the solution is the same. This would be moot if C# supported "infoof" or something to that effect.

You might find this of interest:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=345969&SiteID=1

Matt Warren explains why "infoof" isn't already part of the language.

Krzysztof Koźmic
05/27/2008 06:59 PM by
Krzysztof Koźmic

Well, for the reasons that have been mentioned the best you can do is to have:

        IDuckPond pond = null;

        pond.Stub<IDuckPond, int, Duck>(x => x.GetDuckById);

        pond.Stub<IDuckPond, string, Quack, Duck>(x => x.GetDuckByNameAndQuack);

And then lots of overloads of Stub, to look like this:

    public static void Stub<T, T1, T2>(this T item, Expression<Func<T, Func<T1, T2>>> expression)

    {

        //body

    }

    public static void Stub<T, T1, T2, T3>(this T item, Expression<Func<T, Func<T1, T2, T3>>> expression)

    {

        //body

    }

I dont think that's the way to go. With current syntax I dont really thing there's a good solution for this.

James Curran
05/27/2008 08:55 PM by
James Curran

I believe I can answer the question, if you clarify something

Given "pond.Stub( x => x.GetDuckByNameAndQuack);" compiling correctly, where would "name" and "q" come from?

Paul Batum
05/27/2008 10:04 PM by
Paul Batum

So I gather that we are talking about making it compile without having to put something like this alongside the IDuckPond definition?

static class IDuckPondStub

{

    public static void Stub(this IDuckPond pond, Func<IDuckPond , Func<int, Duck>> action) {}

    public static void Stub(this IDuckPond pond, Func<IDuckPond, Func<string, Quack, Duck>> action){}

}

Pretty trivial to code generate, so I guess you are not interested in doing that?

Ayende Rahien
05/27/2008 10:19 PM by
Ayende Rahien

I don't care if I have to do this, just as long as I don't have to do this per object that I am mocking

James Curran
05/27/2008 10:48 PM by
James Curran

My question wasn't as clear as I'd like.

Given

pond.Stub( x => x.GetDuckByNameAndQuack);

presumably there will be a line which starts

Duck selectedDuck = ???????????

what's the end of that line?

Ayende Rahien
05/27/2008 10:52 PM by
Ayende Rahien

There is not such line.

For the purpose of what I am talking about here, I don't care about actually calling this.

Paul Batum
05/27/2008 11:14 PM by
Paul Batum

Another relatively obvious implementation that is very close to Krzysztof's suggestion but relies on generic parameter inference and not stub overloads:

d.Stub(x => (Func<int,Duck>) x.GetDuckById);

d.Stub(x => (Func<string, Quack, Duck>) x.GetDuckByNameAndQuack);

public static void Stub<T, U>(this T obj, Func<T, U> func) { }

Again, not an ideal solution I know.. just throwing it out there...

James Curran
05/28/2008 02:10 AM by
James Curran

There is not such line. For the purpose of what I am talking about here, I don't care about actually calling this. <<

So we have an implementation, without a (given) purpose. That's kinda like says "I want to walk North, but there's a wall in the way". The real question is "where do you want to go?" then we work on how to get there...

Sashi
05/28/2008 06:08 AM by
Sashi

Ayende - please elaborate your problem. People here are having different understandings.

Ayende Rahien
05/28/2008 07:37 AM by
Ayende Rahien

No, the purpose here is to get this to compile, because that it is the only hurdle on the way.

I don't want to have any external issue with the issue at hand.

Once you get that to compile, we can discuss how we can use this feature, but until we do, there is not point in moving on

Ayende Rahien
05/28/2008 07:48 AM by
Ayende Rahien

Sashi,

There is nothing to elaborate. I want a way to get the above code to compile, that is all.

Bryan Watts
05/28/2008 03:42 PM by
Bryan Watts

Oren,

I see what you are going for now. The code as you have written it will never be valid because it doesn't actually describe your intent.

From the compiler's perspective, you are missing information: the parameters which determines the method's signature. No sane compiler would attempt to "guess" which one you meant to invoke.

The point is that a method name by itself doesn't mean what you want it to mean. Your code is akin to walking into the house of a family and asking for a particular person by last name only. Everyone is going to look at you funny until you tell them something more.

Jacob
05/28/2008 04:31 PM by
Jacob

would you accept:

IDuckPond pond = null;

pond.Stub(x => x.GetDuckById, default(int));

pond.Stub(p => p.GetDuckByNameAndQuack, default(string), default(Quack));

Implemented by:

public static void Stub<T, T1>(this T instance, Func<T, Func<T1, object>> fn,

    T1 arg1) { }

public static void Stub<T, T1, T2>(this T instance, Func<T, Func<T1, T2, object>> fn,

    T1 arg1, T2 arg2) { }

/* etc. */
Ayende Rahien
05/28/2008 04:32 PM by
Ayende Rahien

Bryan,

Yes, I understand what the problem is.

I am just not happy about it.

Ayende Rahien
05/28/2008 04:36 PM by
Ayende Rahien

Jacob,

That is probably the best solution, but I don't like it.

Jacob
05/28/2008 05:00 PM by
Jacob

Yeah; I can understand that.

This one's even more verbose (and pretty silly)--but is somehow charming:

IDuckPond pond = null;

pond.Stub(x => x.GetDuckById, id => default(int));

pond.Stub(x => x.GetDuckByNameAndQuack,

name => default(string),

quack => default(Quack));

Implementation:

public static void Stub<T, T1>(this T instance, Func<T, Func<T1, object>> fn,

Func<object, T1> arg1) { }

public static void Stub<T, T1, T2>(this T instance, Func<T, Func<T1, T2, object>> fn,

Func<object, T1> arg1, Func<object, T2> arg2) { }
Bryan Watts
05/28/2008 05:05 PM by
Bryan Watts

Oren,

I guess what I'm wondering is: without resorting to arbitrary resolution, how could the problem of "method reference by name only" be different? Not just in C# land, in general.

What is the intent behind your proposed syntax? It doesn't read as anything meaningful to me. How would you state your intent in natural language?

Ayende Rahien
05/28/2008 05:12 PM by
Ayende Rahien

Bryan,

I am try to say "when you can method A, regardless of its parameters".

I would handle this by allowing it if there are no overloads, and require disambiguation only if it is neccesary.

Bryan Watts
05/28/2008 05:59 PM by
Bryan Watts

Ok, so the general problem is "overload resolution with less-than-sufficient information."

Eric Lippert addressed this issue; return type inference on method groups is what gets you. He says it better than I can:

http://blogs.msdn.com/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

The final stance seems to be that the rules you are suggesting, Oren, cause confusing overload resolution rules which would violate the Principle of Least Surprise and be a source of subtle bugs.

I would be interested to hear your thoughts on that article.

Paul Batum
05/28/2008 06:02 PM by
Paul Batum

Hey Jacob I like that last one, I agree even though its more verbose its nice. Maybe because it reminds me of passing parameters to a ruby method using a hash.

Anonymous
05/28/2008 06:37 PM by
Anonymous

Further to the link that Bryan supplied, Eric Lippert just wrote a post today to follow up on this issue.

http://blogs.msdn.com/ericlippert/archive/2008/05/28/method-type-inference-changes-part-zero.aspx

Ayende Rahien
05/28/2008 07:55 PM by
Ayende Rahien

Bryan,

From my perspective, this is throwing the baby with the bathwater.

If it it possible to make a best-effort attempt, I think that it should be done.

Sure, there would be pathological cases, but to block the feature because we can't handle them seems excessive to me.

Bryan Watts
05/28/2008 08:43 PM by
Bryan Watts

I agree. I was happy to read the post by Anonymous indicating the entire enterprise is being reworked.

Bryan Watts
05/28/2008 08:44 PM by
Bryan Watts

Or rather, the post by Eric Lippert referred by Anonymous.

Comments have been closed on this topic.