読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

C#でもLazy Evaluationしたいよね。カリー化しーの、遅延評価しーの、トツギーノ。

プログラミング C#3.0 C#4.0

.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
カリー化しーの、遅延評価しーの、トツギーノ。


わーい。カリー化しーの、遅延評価しーの、トツギーノ
できたよー(´∀`)ノ