C#でもLazy Evaluationしたいよね。カリー化しーの、遅延評価しーの、トツギーノ。
.NET Framework4.0には System.Lazy
もしかして遅延評価標準サポートktkr?と、思わずwktkした人も少なくないのでは。
ですが、これはインスタンスの生成を遅らせるだけの代物でした。多くのC#erの期待は一瞬にして儚く散ってしまったのです。
C#でもLazy Evaluationしてみよう。
ということで、標準ではサポートされてないんだけど、やっぱりC#でも遅延評価したいというニーズはあると思うわけで。
以前作ったLazyなEvaluuation機能を投下しておきます。
以下、C#で遅延評価機能を提供するのサンプルコード。
using System; using System.Collections.Generic; namespace ClassLibrary1 { /// <summary> /// 遅延評価 /// </summary> public static class Lazy { #region Action /// <summary> /// Actionを遅延評価します。 /// </summary> /// <typeparam name="TArgs"></typeparam> /// <param name="action"></param> /// <returns></returns> public static IEnumerable<LazyItem> Delay(this Action action) { action(); yield return new LazyItem(); } /// <summary> /// Action{TArgs}を遅延評価します。 /// </summary> /// <typeparam name="TArgs"></typeparam> /// <param name="action"></param> /// <param name="args"></param> /// <returns></returns> public static IEnumerable<LazyItem> Delay<TArgs>(this Action<TArgs> action, TArgs args) { action(args); yield return new LazyItem(); } /// <summary> /// Action{TArgs1, TArgs2}を遅延評価します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <param name="action"></param> /// <param name="args1"></param> /// <param name="args2"></param> /// <returns></returns> public static IEnumerable<LazyItem> Delay<TArgs1, TArgs2>(this Action<TArgs1, TArgs2> action, TArgs1 args1, TArgs2 args2) { action(args1, args2); yield return new LazyItem(); } /// <summary> /// Action{TArgs1, TArgs2, TArgs3}を遅延評価します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <param name="action"></param> /// <param name="args1"></param> /// <param name="args2"></param> /// <returns></returns> public static IEnumerable<LazyItem> Delay<TArgs1, TArgs2, TArgs3>(this Action<TArgs1, TArgs2, TArgs3> action, TArgs1 args1, TArgs2 args2, TArgs3 args3) { action(args1, args2, args3); yield return new LazyItem(); } #endregion #region Func /// <summary> /// Func{TResult}を遅延評価します。 /// </summary> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <returns></returns> public static IEnumerable<LazyItem<TResult>> Delay<TResult>(this Func<TResult> f) { yield return new LazyItem<TResult>(f()); } /// <summary> /// Func{TArgs, TResult}を遅延評価します。 /// </summary> /// <typeparam name="TArgs"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <param name="args"></param> /// <returns></returns> public static IEnumerable<LazyItem<TResult>> Delay<TArgs, TResult>(this Func<TArgs, TResult> f, TArgs args) { yield return new LazyItem<TResult>(f(args)); } /// <summary> /// Func{TArgs1, TArgs2, TResult}を遅延評価します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <param name="args1"></param> /// <param name="args2"></param> /// <returns></returns> public static IEnumerable<LazyItem<TResult>> Delay<TArgs1, TArgs2, TResult>(this Func<TArgs1, TArgs2, TResult> f, TArgs1 args1, TArgs2 args2) { yield return new LazyItem<TResult>(f(args1, args2)); } /// <summary> /// Func{TArgs1, TArgs2, TArgs3, TResult}を遅延評価します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <typeparam name="TArgs3"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <param name="args1"></param> /// <param name="args2"></param> /// <param name="args3"></param> /// <returns></returns> public static IEnumerable<LazyItem<TResult>> Delay<TArgs1, TArgs2, TArgs3, TResult>(this Func<TArgs1, TArgs2, TArgs3, TResult> f, TArgs1 args1, TArgs2 args2, TArgs3 args3) { yield return new LazyItem<TResult>(f(args1, args2, args3)); } #endregion /// <summary> /// 遅延していたFuncの評価を実施します。 /// </summary> /// <typeparam name="TResult"></typeparam> /// <param name="enumerable"></param> /// <returns></returns> public static TResult Force<TResult>(this IEnumerable<LazyItem<TResult>> lazyEnumerable) { var itr = lazyEnumerable.GetEnumerator(); if (itr.MoveNext()) return itr.Current.Value; throw new ArgumentException("Forceできねっす", "lazyEnumerable"); } /// <summary> /// 遅延していたActionの評価を実施します。 /// </summary> /// <typeparam name="TResult"></typeparam> /// <param name="enumerable"></param> public static void Force(this IEnumerable<LazyItem> lazyEnumerable) { var itr = lazyEnumerable.GetEnumerator(); if (itr.MoveNext()) return; throw new ArgumentException("Forceできねっす", "lazyEnumerable"); } } /// <summary> /// FuncのLazyの入れ物 /// </summary> /// <typeparam name="TResult"></typeparam> public class LazyItem<TResult> { public TResult Value { get; private set; } public LazyItem(TResult value) { Value = value; } } /// <summary> /// ActionのLazyの入れ物 /// 完全にダミー /// </summary> public class LazyItem { } }
yield return の特性を活かして無理矢理 Lazy Evaluation を再現しているのがわかると思います。
Funcのyield returnで返すLazyItemは評価した値を保持していますが、
Actionのyield returnで返すLazyItemは、実装の都合上のダミーです。
C#でカリー化
カリー化と遅延評価は相性抜群ですよね−、というか、複数の引数をとる関数を随時カリー化しつつ、最後の引数を与えるときはディレイして遅延評価にしておくっゆー使い方ができると、
なにかと嬉しい感じなので、カリー化機能も用意しておくと幸せになれるような気がします。
以下、C#でカリー化機能を提供するのサンプルコード。
using System; namespace ClassLibrary1 { //C#3.0ではこれが必要 //public delegate TResult Func<T1, T2, T3, T4, T5, TResult>(T1 a, T2 b, T3 c, T4 d, T5 e); /// <summary> /// カリー化を提供します。 /// </summary> public static class CurryExtentions { /// <summary> /// Func{TArgs1, TArgs2, TResult}についてカリー化します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <returns></returns> public static Func<TArgs1, Func<TArgs2, TResult>> Curry<TArgs1, TArgs2, TResult> (this Func<TArgs1, TArgs2, TResult> f) { return a => b => f(a, b); } /// <summary> /// Func{TArgs1, TArgs2, TArgs3, TResult}についてカリー化します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <typeparam name="TArgs3"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <returns></returns> public static Func<TArgs1, Func<TArgs2, Func<TArgs3, TResult>>> Curry<TArgs1, TArgs2, TArgs3, TResult> (this Func<TArgs1, TArgs2, TArgs3, TResult> f) { return a => b => c => f(a, b, c); } /// <summary> /// Func{TArgs1, TArgs2, TArgs3, TArgs4, TResult}についてカリー化します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <typeparam name="TArgs3"></typeparam> /// <typeparam name="TArgs4"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <returns></returns> public static Func<TArgs1, Func<TArgs2, Func<TArgs3, Func<TArgs4, TResult>>>> Curry<TArgs1, TArgs2, TArgs3, TArgs4, TResult> (this Func<TArgs1, TArgs2, TArgs3, TArgs4, TResult> f) { return a => b => c => d => f(a, b, c, d); } /// <summary> /// Func{TArgs1, TArgs2, TArgs3, TArgs4, TArgs5, TResult}についてカリー化します。 /// </summary> /// <typeparam name="TArgs1"></typeparam> /// <typeparam name="TArgs2"></typeparam> /// <typeparam name="TArgs3"></typeparam> /// <typeparam name="TArgs4"></typeparam> /// <typeparam name="TArgs5"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="f"></param> /// <returns></returns> public static Func<TArgs1, Func<TArgs2, Func<TArgs3, Func<TArgs4, Func<TArgs5, TResult>>>>> Curry<TArgs1, TArgs2, TArgs3, TArgs4, TArgs5, TResult> (this Func<TArgs1, TArgs2, TArgs3, TArgs4, TArgs5, TResult> f) { return a => b => c => d => e => f(a, b, c, d, e); } } }
.NET Framework4.0からは、ジェネリックデリゲートが大量追加されいます。Funcについては、
Func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult) デリゲート
まで提供されているので、以前「カレー好きのための、ラムダ式でカリー化な拡張メソッド。」で書いたものを
標準サポートのFuncデリゲートで書き換えてみました。うはwFuncデリゲートの階層深杉ワロスw
というか、引数16個ってどんだけですか。これはたぶん使う機会ないw
カリー化しーの、遅延評価しーの、トツギーノ。
では、早速お試ししてみます。
using System; using ClassLibrary1; namespace ConsoleApplication1 { class Program { static void Main() { Action(); Console.WriteLine(); Func(); Console.WriteLine(); Curry(); Console.ReadKey(); } static void Action() { var lazya0 = Lazy.Delay(() => Console.WriteLine("Hello,World!")); var lazya1 = Lazy.Delay(x => Console.WriteLine("Hello,{0}", x), "World!"); Action<string, string> act2 = (x, y) => Console.WriteLine("{0},{1}", x, y); var lazya2 = act2.Delay("Hello", "World!"); Action<string, string, string> act3 = (x, y, z) => Console.WriteLine("{0}{1}{2}", x, y, z); var lazya3 = act3.Delay("Hello", ",", "World!"); Console.WriteLine("-*Action"); lazya0.Force(); lazya1.Force(); lazya2.Force(); lazya3.Force(); } static void Func() { var lazyf0 = Lazy.Delay(() => 4); var lazyf1 = Lazy.Delay(x => Math.Pow(x, 2), 3); var lazyf2 = Lazy.Delay((x, y) => x * y, 2, 8); Func<int, int, int, int> f3 = (x, y, z) => x + y - z; var lazyf3 = f3.Delay(18, 12, 5); Console.WriteLine("-*Func"); Console.WriteLine(lazyf0.Force()); Console.WriteLine(lazyf1.Force()); Console.WriteLine(lazyf2.Force()); Console.WriteLine(lazyf3.Force()); } static void Curry() { Func<decimal, decimal, decimal, decimal, decimal, decimal> f = TestMethod; var cf = f.Curry(); var lazyc1 = cf(1)(2)(3)(4).Delay(5); Func<string, string, string> f2 = (x,y) => x + "しーの、" + y + "しーの、" + "トツギーノ。"; var cf2 = f2.Curry(); //ここに注目 var lazyc2 = cf2("カリー化").Delay("遅延評価"); Console.WriteLine("-*Curry"); Console.WriteLine(lazyc1.Force()); Console.WriteLine(lazyc2.Force()); Console.ReadLine(); } public static decimal TestMethod(decimal a, decimal b, decimal c, decimal d, decimal e) { return ((a + b) * c - d) / e; } } }
実行結果
-*Action Hello,World! Hello,World! Hello,World! Hello,World! -*Func 4 9 16 25 -*Curry 1 カリー化しーの、遅延評価しーの、トツギーノ。
わーい。カリー化しーの、遅延評価しーの、トツギーノ。
できたよー(´∀`)ノ