Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 5,953 | Comments: 44,408

filter by tags archive

ChallengeStriving 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.

More posts in "Challenge" series:

  1. (28 Apr 2015) What is the meaning of this change?
  2. (26 Sep 2013) Spot the bug
  3. (27 May 2013) The problem of locking down tasks…
  4. (17 Oct 2011) Minimum number of round trips
  5. (23 Aug 2011) Recent Comments with Future Posts
  6. (02 Aug 2011) Modifying execution approaches
  7. (29 Apr 2011) Stop the leaks
  8. (23 Dec 2010) This code should never hit production
  9. (17 Dec 2010) Your own ThreadLocal
  10. (03 Dec 2010) Querying relative information with RavenDB
  11. (29 Jun 2010) Find the bug
  12. (23 Jun 2010) Dynamically dynamic
  13. (28 Apr 2010) What killed the application?
  14. (19 Mar 2010) What does this code do?
  15. (04 Mar 2010) Robust enumeration over external code
  16. (16 Feb 2010) Premature optimization, and all of that…
  17. (12 Feb 2010) Efficient querying
  18. (10 Feb 2010) Find the resource leak
  19. (21 Oct 2009) Can you spot the bug?
  20. (18 Oct 2009) Why is this wrong?
  21. (17 Oct 2009) Write the check in comment
  22. (15 Sep 2009) NH Prof Exporting Reports
  23. (02 Sep 2009) The lazy loaded inheritance many to one association OR/M conundrum
  24. (01 Sep 2009) Why isn’t select broken?
  25. (06 Aug 2009) Find the bug fixes
  26. (26 May 2009) Find the bug
  27. (14 May 2009) multi threaded test failure
  28. (11 May 2009) The regex that doesn’t match
  29. (24 Mar 2009) probability based selection
  30. (13 Mar 2009) C# Rewriting
  31. (18 Feb 2009) write a self extracting program
  32. (04 Sep 2008) Don't stop with the first DSL abstraction
  33. (02 Aug 2008) What is the problem?
  34. (28 Jul 2008) What does this code do?
  35. (26 Jul 2008) Find the bug fix
  36. (05 Jul 2008) Find the deadlock
  37. (03 Jul 2008) Find the bug
  38. (02 Jul 2008) What is wrong with this code
  39. (05 Jun 2008) why did the tests fail?
  40. (27 May 2008) Striving for better syntax
  41. (13 Apr 2008) calling generics without the generic type
  42. (12 Apr 2008) The directory tree
  43. (24 Mar 2008) Find the version
  44. (21 Jan 2008) Strongly typing weakly typed code
  45. (28 Jun 2007) Windsor Null Object Dependency Facility

Comments

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Sashi,

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

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

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

Bryan,

Yes, I understand what the problem is.

I am just not happy about it.

Ayende Rahien

Jacob,

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

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

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

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

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

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

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

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

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

Bryan Watts

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

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. The RavenDB Comic Strip (3):
    28 May 2015 - Part III – High availability & sleeping soundly
  2. Special Offer (2):
    27 May 2015 - 29% discount for all our products
  3. RavenDB Sharding (3):
    22 May 2015 - Adding a new shard to an existing cluster, splitting the shard
  4. Challenge (45):
    28 Apr 2015 - What is the meaning of this change?
  5. Interview question (2):
    30 Mar 2015 - fix the index
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats