taediumの日記

2012-09-08

[] 継承やインタフェースはほんとうにいらないかもしれない

http://blog.livedoor.jp/gab_km/archives/1425937.html

だって、F#にはレコードがありますから。(いまさらのリアクションでごめんなさい)

type Animal = { name: string; bark: unit->string }

let makeDog name = 
  { name = name ; bark = fun () -> "Bow wow!"}

let makeCat name = 
  { name = name ; bark = fun () -> "meow"}

(* 継承っぽいこと *)
let makeTiger name =
  let cat = makeCat name
  { cat with bark = fun () -> cat.bark() + "!!!" }

let animals = [ makeDog "Pochi"; makeCat "Tama"; makeTiger "Tora" ]

animals 
|> Seq.map (fun a -> a.bark()) 
|> Seq.iter (printf "%A ")       // "Bow wow!" "meow" "meow!!!" 

F#に閉じて書くんなら継承やインタフェースはほんとうに使わなくて済むと思います。C#のコードから呼び出す、とかC#向けに拡張ポイント作るとなると大体のケースで避けられないですけど。

[] 部分適用の使いどころ

部分適用というのは、簡単に言ってしまえば、関数の型を変換する方法の1つです。

たとえば、Func<int, int, int> を Func<int, int> に変換するとか。

部分適用という言葉を使わずとも、これはよく行われていることで、ラムダ式を使って実現されます。

わかりやすいように、型を明示してサンプルコード書いてみます。

        [TestMethod]
        public void TestLambda()
        {
            Func<int, int, int> add = (x, y) => x + y;
            int a = 100;

            // ラムダ式で Func<int, int, int> から Func<int, int> に型を変換
            Func<int, int> selector = y => add(a, y);

            IEnumerable<int> result = Enumerable.Range(0, 10).Select(selector);
            Assert.IsTrue(result.SequenceEqual(Enumerable.Range(100, 10)));
        }

自分は、これを、関数をラムダ式でwrapしてadaptするみたいに表現します。


上記のコードを部分適用を使って書くとこんな感じになります(前のエントリで定義した拡張メソッドPartialを使います)。

        [TestMethod]
        public void TestPartialApplication()
        {
            Func<int, int, int> add = (x, y) => x + y;
            int a = 100;

            // 部分適用で Func<int, int, int> から Func<int, int> へ型を変換
            Func<int, int> selector = add.Partial(a);

            IEnumerable<int> result = Enumerable.Range(0, 10).Select(selector);
            Assert.IsTrue(result.SequenceEqual(Enumerable.Range(100, 10)));
        }

見た目的には、「y => add(a, y)」が「add.Partial(a)」に変わっただけだったりします。

大して変わらないといえば、そのとおりです。

強いてポイントを挙げるならば、ラムダ式にとって変数「a」は自由変数ですがが、部分適用においては、変数aが引数として扱われるということですね。部分変数のほうが変数の共有によるバグを避けられるといえるかもしれない。それから、部分適用のほうがスコープの入れ子がない分だけ単純です。

もちろん、部分適用より、ラムダ式による型変換のほうが柔軟です。なんでもできる。部分適用は、Func<string, string, int> を Func<int, int> に変換するようなことはできないですが、ラムダ式を使えばなんでもできます。


まとめ

部分適用は関数の型変換における単純ケースの1つ。なので、そういう型変換が適切な場合に使える、と考えるのがわかりやすいです。

ちなみ、C#で部分適用を使ったほうがいいかはどうかは微妙です。やっぱり、言語の構文に組み込まれていてシンプルに使える場合に効果を発揮しやすいんじゃないかなと思います(F#のようにね!)。

[] C#でカリー化と部分適用

勉強会で話題になっておもしろかったので、実装してみました。


カリー化

4つの引数まで対応したFuncの拡張メソッド。

    public static class FuncCurry
    {
        public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> f)
        {
            return arg1 => arg2 => f.Invoke(arg1, arg2);
        }

        public static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> f)
        {
            return arg1 => arg2 => arg3 => f.Invoke(arg1, arg2, arg3);
        }

        public static Func<T1, Func<T2, Func<T3, Func<T4, TResult>>>> Curry<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> f)
        {
            return arg1 => arg2 => arg3 => arg4 => f.Invoke(arg1, arg2, arg3, arg4);
        }	
    }

テストコード。

    [TestClass]
    public class FuncCurryTest
    {

        [TestMethod]
        public void Test2Args()
        {
            Func<int, int, int> add = (a, b) => a + b;
            Assert.AreEqual(3, add(1, 2));

            var f = add.Curry();
            Assert.AreEqual(3, f(1)(2));
        }

        [TestMethod]
        public void Test3Args()
        {
            Func<int, int, int, int> add = (a, b, c) => a + b + c;
            Assert.AreEqual(6, add(1, 2, 3));

            var f = add.Curry();
            Assert.AreEqual(6, f(1)(2)(3));
        }

        [TestMethod]
        public void Test4Args()
        {
            Func<int, int, int, int, int> add = (a, b, c, d) => a + b + c + d;
            Assert.AreEqual(10, add(1, 2, 3, 4));

            var f = add.Curry();
            Assert.AreEqual(10, f(1)(2)(3)(4));
        }
    }

通常の呼び出し方と比べると、違いは一目瞭然ですね。

拡張メソッドのCurryを呼び出すと、1つの引数を受け取る関数をチェーンできます。


部分適用

4つの引数まで対応したFuncの拡張メソッド。

    public static class FuncPartial
    {
        public static Func<T2, TResult> Partial<T1, T2, TResult>(this Func<T1, T2, TResult> f, T1 arg1)
        {
            return arg2 => f.Invoke(arg1, arg2);
        }

        public static Func<T3, TResult> Partial<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> f, T1 arg1, T2 arg2)
        {
            return arg3 => f.Invoke(arg1, arg2, arg3);
        }

        public static Func<T2, T3, TResult> Partial<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> f, T1 arg1)
        {
            return (arg2, arg3) => f.Invoke(arg1, arg2, arg3);
        }

        public static Func<T4, TResult> Partial<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> f, T1 arg1, T2 arg2, T3 arg3)
        {
            return arg4 => f.Invoke(arg1, arg2, arg3, arg4);
        }

        public static Func<T3, T4, TResult> Partial<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> f, T1 arg1, T2 arg2)
        {
            return (arg3, arg4) => f.Invoke(arg1, arg2, arg3, arg4);
        }

        public static Func<T2, T3, T4, TResult> Partial<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> f, T1 arg1)
        {
            return (arg2, arg3, arg4) => f.Invoke(arg1, arg2, arg3, arg4);
        }
    }

テストコード。

    [TestClass]
    public class FuncPartialTest
    {
        [TestMethod]
        public void Test2Args()
        {
            Func<int, int, int> add = (a, b) => a + b;
            Assert.AreEqual(3, add(1, 2));

            var add1 = add.Partial(1);
            Assert.AreEqual(3, add1(2));
        }

        [TestMethod]
        public void Test3Args()
        {
            Func<int, int, int, int> add = (a, b, c) => a + b + c;
            Assert.AreEqual(6, add(1, 2, 3));

            var add1 = add.Partial(1);
            Assert.AreEqual(6, add1(2, 3));

            var add12 = add.Partial(1, 2);
            Assert.AreEqual(6, add12(3));
        }

        [TestMethod]
        public void Test4Args()
        {
            Func<int, int, int, int, int> add = (a, b, c, d) => a + b + c + d;
            Assert.AreEqual(10, add(1, 2, 3, 4));

            var add1 = add.Partial(1);
            Assert.AreEqual(10, add1(2, 3, 4));

            var add12 = add.Partial(1, 2);
            Assert.AreEqual(10, add12(3, 4));

            var add123 = add.Partial(1, 2, 3);
            Assert.AreEqual(10, add123(4));
        }
    }

拡張メソッドのPartialで引数の一部を渡すと、残りの引数を受け取る関数を返します。


部分適用(引数入れ替え)

第1引数と第2引数を入れ替えるFuncの拡張メソッド。

    public static class FuncFlip
    {
        public static Func<T2, T1, TResult> Flip<T1, T2, TResult>(this Func<T1, T2, TResult> f)
        {
            return (arg1, arg2) => f.Invoke(arg2, arg1);
        }
    }

部分適用と組み合わせたテストコード。

    [TestClass]
    public class FuncFlipTest
    {
        [TestMethod]
        public void TestFlip()
        {
            Func<decimal, decimal, decimal> div = (a, b) => a / b;

            var f = div.Partial(2M);
            Assert.AreEqual(0.2M, f(10M)); // 2 / 10

            var g = div.Flip().Partial(2M);
            Assert.AreEqual(5M, g(10M));   // 10 / 2
        }
    }

まとめ

カリー化と部分適用は混同しがちですが、違うものですね。

カリー化は、使いどころをあんまり思い浮かばないのですが、部分適用は便利な場合がそこそこあると思います。