In the comments of my last post, Peter Seale pointed me to Matthew Podwysocki‘s implementation of GenerateUsing as functional abstraction of using
. I like the idea, but the Generator pattern isn’t particularly useful for common SharePoint tasks. However, I think we can get some value from looking at a more generalized solution.
But first, let me suggest a minor correction to Matt’s version of Generate
, at least if it’s going to be used to fulfill an IDisposable
contract:
public static IEnumerable<TResult> Generate<T, TResult>(Func<T> opener, Func<T, Option<TResult>> generator, Action<T> closer) { var openerResult = opener(); bool stop = false; while (true) { var res = Option<TResult>.None; try { res = generator(openerResult); } finally { if (stop = res.IsNone) closer(openerResult); } if (stop) yield break; yield return res.Value; } }
The stop
“hack” is needed because you can’t yield
from a finally
clause. It seems to me that a closer
that might not get called isn’t much of a closer
, or am I missing something?
So how else might we use this opener/closer idea? How about something like this:
public static void Process<T>(Func<T> opener, Action<T> action, Action<T> closer) { T openerResult = opener(); try { action(openerResult); } finally { if (closer != null) closer(openerResult); } } public static void Using<T>(Func<T> opener, Action<T> action ) where T : IDisposable { Process(opener, action, x => x.Dispose()); }
We have now abstracted the idea of a using
statement: get the object, do something with it, Dispose()
. Abstraction in hand, let’s apply it to SharePoint:
public static void ProcessWeb<TResult>(this SPSite site, string url, Action<SPWeb> action) { Using(() => site.OpenWeb(url), action); }
Now, one could argue that we haven’t gained much over the obvious implementation:
using(SPWeb web = site.OpenWeb()) action(web);
But in truth, the vast majority of functional code has a non-functional alternative. It’s just a different thought process. In the former, we specify what we’re trying to do: use the result of site.OpenWeb()
to do action
. In the latter, we specify how to do it: use an SPWeb
named web
, assigned from site.OpenWeb()
, to perform action
. I’m not saying either approach is more correct, just different means to the same end.
Performing actions is all well and good, but we often want to get something back as well:
public TResult Select<T, TResult>(Func<T> opener, Func<T, TResult> selector, Action<T> closer) { T openerResult = opener(); try { return selector(openerResult); } finally { closer(openerResult); } } public TResult SelectUsing<T, TResult>(Func<T> opener, Func<T, TResult> selector, Action<T> closer ) where T : IDisposable { return Select(opener, selector, x => x.Dispose()); } public static TResult SelectFromWeb<TResult>(this SPSite site, string url, Func<SPWeb, TResult> selector) { return SelectUsing(() => site.OpenWeb(url), selector); }
What do you think? Useful?
February 22, 2009 at 7:17 pm
[…] Thinking Functional: Using […]
March 19, 2009 at 12:40 am
[…] Thinking Functional: Using […]
July 23, 2009 at 2:29 am
[…] Put the using statement in a method (named or anonymous) that is called from the query. See also: Thinking Functional: Using. […]
July 23, 2009 at 2:32 am
[…] Put the using statement in a method (named or anonymous) that is called from the query. See also: Thinking Functional: Using. […]