There's a great post on implementing idioms with LINQ, and the example application was to implement formlets, as WebSharper does for F#. Tomas's post is well written, so if you're unclear on the above concepts I recommend reading it first before proceeding with this article.
The claim in that post is that idioms can only be encoded via LINQ's 'join' operators. While strictly true if you stick to all the LINQ rules, because LINQ queries are just naive syntactic transforms you don't have to follow the rules. You can thus exploit this to hijack the signatures for the SelectMany overloads to yield idiom signatures. It's not all sunshine and roses though, as there are consequences.
LINQ is a standard set of methods one can implement that the C# compiler can use to provide "query patterns". This query:
var foo = from x in SomeFoo from y in foo.Values select y;is translated by the C# compiler to:
var foo = SomeFoo.SelectMany(x => x.Values, (x, y) => y);This is a purely syntactic transformation, meaning that the C# compiler simply takes the text from above, and naively translates each 'from', 'where', 'select', etc. into calls to instance or extension methods, SelectMany, Where, Select, etc. Type inference must then be able to infer the types used in your query, and everything must type check.
The fact that we're dealing with a purely syntactic transform means that we can be sneaky and alter the signatures of these LINQ functions and the C# compiler would be none the wiser. The resulting calls to the LINQ methods would still need to compile, but we can ensure that they only compile following the rules we want, in this case, of the rules of idioms.
The core LINQ methods are as follows, using Formlet<T> as the LINQ type:
Formlet<R> Select<T, R>(this Formlet<T> f, Func<T, R> selector); Formlet<R> SelectMany<R>(this Formlet<T> f, Func<T, Formlet<R>> collector); Formlet<R> SelectMany<U, R>(this Formlet<T> f, Func<T, Formlet<U>> Func<T, U, R> selector);The problematic methods for idioms are the two SelectMany calls, specifically, the parameter I've called 'collector'. You can see that the LINQ type is unwrapped and the value extracted on each SelectMany, and passed to the rest of the query. Accessing the previous values like this is forbidden in idioms.
Fortunately, the signatures for SelectMany don't have to have this exact signature, they must only have a similar structure. You must have two SelectMany overloads with one and two delegate parameters, and the first delegate parameter must return your LINQ type, in this case Formlet<T>, as this allows you to chain query clauses one after another. You can also modify the second delegate parameter in various ways, but I haven't found much use for that myself.
To implement idioms, we will simply alter the first delegate parameter so instead of unwrapping the value encapsulated by the Formlet<T>, we simply pass the Formlet<T> itself:
Formlet<R> Select<T, R>(this Formlet<T> f, Func<T, R> selector); Formlet<R> SelectMany<R>(this Formlet<T> f, Func<Formlet<T>, Formlet<R>> collector); Formlet<R> SelectMany<U, R>(this Formlet<T> f, Func<Formlet<T>, Formlet<U>> collector, Func<T, U, R> selector);Our query above:
var foo = from x in SomeFoo from y in foo.Values select y;would then no longer compile, because 'x' is now not a Foo, but is in fact a Formlet<Foo>, and the formlet type does not have a "Values" property. Of course, you shouldn't provide a property to extract the encapsulated value, or this is all for naught.
Simple queries work great, but longer queries may run into some problems if you alter the LINQ signatures. In this case, if you try to access previous values by mistake, as in our example query above, you will get a complicated error message:
Error 1 Could not find an implementation of the query pattern for source type 'Formlet.Formlet<AnonymousType#1>'. '<>h__TransparentIdentifier0' not found.Basically, your incorrect program was naively translated to use the LINQ methods, but because it does not properly match the type signatures you've hijacked, type inference fails. So you can't break your idioms by hijacking the query pattern this way, but depending on your target audience, perhaps you will render them unusable.
Still, it's a neat trick that should be in every type wizard's toolbox.