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:
- (01 Jul 2024) Efficient snapshotable state
- (13 Oct 2023) Fastest node selection metastable error state–answer
- (12 Oct 2023) Fastest node selection metastable error state
- (19 Sep 2023) Spot the bug
- (04 Jan 2023) what does this code print?
- (14 Dec 2022) What does this code print?
- (01 Jul 2022) Find the stack smash bug… – answer
- (30 Jun 2022) Find the stack smash bug…
- (03 Jun 2022) Spot the data corruption
- (06 May 2022) Spot the optimization–solution
- (05 May 2022) Spot the optimization
- (06 Apr 2022) Why is this code broken?
- (16 Dec 2021) Find the slow down–answer
- (15 Dec 2021) Find the slow down
- (03 Nov 2021) The code review bug that gives me nightmares–The fix
- (02 Nov 2021) The code review bug that gives me nightmares–the issue
- (01 Nov 2021) The code review bug that gives me nightmares
- (16 Jun 2021) Detecting livelihood in a distributed cluster
- (21 Apr 2020) Generate matching shard id–answer
- (20 Apr 2020) Generate matching shard id
- (02 Jan 2020) Spot the bug in the stream
- (28 Sep 2018) The loop that leaks–Answer
- (27 Sep 2018) The loop that leaks
- (03 Apr 2018) The invisible concurrency bug–Answer
- (02 Apr 2018) The invisible concurrency bug
- (31 Jan 2018) Find the bug in the fix–answer
- (30 Jan 2018) Find the bug in the fix
- (19 Jan 2017) What does this code do?
- (26 Jul 2016) The race condition in the TCP stack, answer
- (25 Jul 2016) The race condition in the TCP stack
- (28 Apr 2015) What is the meaning of this change?
- (26 Sep 2013) Spot the bug
- (27 May 2013) The problem of locking down tasks…
- (17 Oct 2011) Minimum number of round trips
- (23 Aug 2011) Recent Comments with Future Posts
- (02 Aug 2011) Modifying execution approaches
- (29 Apr 2011) Stop the leaks
- (23 Dec 2010) This code should never hit production
- (17 Dec 2010) Your own ThreadLocal
- (03 Dec 2010) Querying relative information with RavenDB
- (29 Jun 2010) Find the bug
- (23 Jun 2010) Dynamically dynamic
- (28 Apr 2010) What killed the application?
- (19 Mar 2010) What does this code do?
- (04 Mar 2010) Robust enumeration over external code
- (16 Feb 2010) Premature optimization, and all of that…
- (12 Feb 2010) Efficient querying
- (10 Feb 2010) Find the resource leak
- (21 Oct 2009) Can you spot the bug?
- (18 Oct 2009) Why is this wrong?
- (17 Oct 2009) Write the check in comment
- (15 Sep 2009) NH Prof Exporting Reports
- (02 Sep 2009) The lazy loaded inheritance many to one association OR/M conundrum
- (01 Sep 2009) Why isn’t select broken?
- (06 Aug 2009) Find the bug fixes
- (26 May 2009) Find the bug
- (14 May 2009) multi threaded test failure
- (11 May 2009) The regex that doesn’t match
- (24 Mar 2009) probability based selection
- (13 Mar 2009) C# Rewriting
- (18 Feb 2009) write a self extracting program
- (04 Sep 2008) Don't stop with the first DSL abstraction
- (02 Aug 2008) What is the problem?
- (28 Jul 2008) What does this code do?
- (26 Jul 2008) Find the bug fix
- (05 Jul 2008) Find the deadlock
- (03 Jul 2008) Find the bug
- (02 Jul 2008) What is wrong with this code
- (05 Jun 2008) why did the tests fail?
- (27 May 2008) Striving for better syntax
- (13 Apr 2008) calling generics without the generic type
- (12 Apr 2008) The directory tree
- (24 Mar 2008) Find the version
- (21 Jan 2008) Strongly typing weakly typed code
- (28 Jun 2007) Windsor Null Object Dependency Facility
Comments
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 ; )
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<TResult>(Expression<Func<TResult>> 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)));
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
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.
Well, for the reasons that have been mentioned the best you can do is to have:
And then lots of overloads of Stub, to look like this:
I dont think that's the way to go. With current syntax I dont really thing there's a good solution for this.
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?
So I gather that we are talking about making it compile without having to put something like this alongside the IDuckPond definition?
Pretty trivial to code generate, so I guess you are not interested in doing that?
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
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?
There is not such line.
For the purpose of what I am talking about here, I don't care about actually calling this.
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...
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...
Ayende - please elaborate your problem. People here are having different understandings.
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
Sashi,
There is nothing to elaborate. I want a way to get the above code to compile, that is all.
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.
would you accept:
IDuckPond pond = null;
pond.Stub(x => x.GetDuckById, default(int));
pond.Stub(p => p.GetDuckByNameAndQuack, default(string), default(Quack));
Implemented by:
Bryan,
Yes, I understand what the problem is.
I am just not happy about it.
Jacob,
That is probably the best solution, but I don't like it.
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,
Implementation:
public static void Stub<T, T1>(this T instance, Func<T, Func<T1, object>> fn,
public static void Stub<T, T1, T2>(this T instance, Func<T, Func<T1, T2, object>> fn,
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?
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.
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.
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.
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
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.
I agree. I was happy to read the post by Anonymous indicating the entire enterprise is being reworked.
Or rather, the post by Eric Lippert referred by Anonymous.
Comment preview