ようこそ。睡眠不足なプログラマのチラ裏です。

C#からF#のクロージャを利用するには、こんな風にしたらいんじゃないの的サンプル


 

実践 F# 関数型プログラミング入門

実践 F# 関数型プログラミング入門



まず、書籍のご紹介をさせていただきます。
荒井さん、いげ太さん共著の「実践 F# 関数型プログラミング入門 」が好評発売中です。
微力ながらレビュアーの一人としてご協力させていただきました。


基本的な文法からはじまり、関数型言語の特徴について丁寧に解説しています。
読者が新しい情報を順を追って少しずつ手に入れながら、徐々に成長していけるように配慮して構成されています。
F#に興味がある人はもちろん、はじめて関数型言語を勉強しようという方にもおすすめです!
お値段以上ニトリよろしくお買い得すぎるので、.NETerは全員買ったほうがいいと思います。まじで。



C#からF#のクロージャを利用するには

で、小ネタです。


「実践 F# 関数型プログラミング入門 」のコラム p331 F#と他の言語を組み合わせた開発を考える
に言及されているように、これからの.NET開発はF#とC#(VB.NET)を組み合わせた開発が
デファクトスタンダードとなっていくのだろうと思います(とてつもなく時間はかかりそうですが)。
実際、わたしは仕事でF#とC#を組み合わせたソフトウェア開発に今年から取り組んでいきます(やったね!)。
ということを考えた場合…、C#で作ったライブラリはF#から容易に呼び出したいし、F#で作ったライブラリはC#から容易に呼び出したいわけです。
そこで最もネックとなるのが、F#のクロージャを受け取る関数をC#から呼びだそうとする場合です。
System.Func<>は適切なFSharpFunc<_>型へ変換する必要があるからです。


「実践 F# 関数型プログラミング入門 」のコラム p317 C#からF#のクロージャを利用するには
に少し解説がありますが、具体的にはどうしたらよいのでしょうか。



以前書いた記事の中でわたしは、C#側からF#のクロージャを受け取る関数を呼び出すために以下のような関数をいくつか定義しました。

public static FSharpFunc<TArg1, TResult> ToFSharpFunc<TArg1, TResult>(this Func<TArg1, TResult> func)
{
    return FuncConvert.ToFSharpFunc(new Converter<TArg1, TResult>(func));
}

public static FSharpFunc<TArg,Unit> ToFSharpFunc<TArg>(this Action<TArg> action)
{
    return FuncConvert.ToFSharpFunc(action);
}


これらはいずれも引数が1つの場合に限られます。
引数が増えると、とんでもなく面倒くさいことになります以下のように。

public static FSharpFunc<TArg1, FSharpFunc<TArg2,TResult>> ToFSharpFunc<TArg1, TArg2, TResult>(this Func<TArg1,TArg2,TResult> func)
{
    Converter<TArg1, FSharpFunc<TArg2, TResult>> conv = value1 =>
        {
            return ToFSharpFunc<TArg2,TResult>(value2 => func(value1, value2));
        };
    return FSharpFunc<TArg1, FSharpFunc<TArg2, TResult>>.FromConverter(conv);
}


いやはや、実に「C#らしいコード(笑)」が好きな方が好きそうなコードですねw
T4などを用いるなどしてC#で実装するというのなら、別に止めたりはしませんが。
こんなにC#らしすぎるコードを大量に吐き出されても…、なんだかなーという気がします。
たぶん、F#で実装した方が素直です。


以下、F#側でSystem.Func<>からFSharpFunc<_>への変換を支援する関数の実装サンプルです。

namespace Microsoft.FSharp.Core
open System
open System.Linq.Expressions
open System.Runtime.CompilerServices 

[<Extension>]
module Util =
  type public CSharpFunc = 
    static member internal ToFSharpFunc<'a> (action:Action<'a>) = fun a -> action.Invoke(a)
    static member internal ToFSharpFunc<'a,'b> (action:Action<'a,'b>) = fun a b -> action.Invoke(a,b)
    static member internal ToFSharpFunc<'a,'b,'c> (action:Action<'a,'b,'c>) = fun a b c -> action.Invoke(a,b,c)
    static member internal ToFSharpFunc<'a,'b,'c,'d> (action:Action<'a,'b,'c,'d>) = fun a b c d -> action.Invoke(a,b,c,d)
    static member internal ToFSharpFunc<'a,'b,'c,'d,'e> (action:Action<'a,'b,'c,'d,'e>) = fun a b c d e -> action.Invoke(a,b,c,d,e)

    static member internal ToFSharpFunc<'a,'b> (func:Func<'a,'b>) = fun a -> func.Invoke(a)
    static member internal ToFSharpFunc<'a,'b,'c> (func:Func<'a,'b,'c>) = fun a b -> func.Invoke(a,b)
    static member internal ToFSharpFunc<'a,'b,'c,'d> (func:Func<'a,'b,'c,'d>) = fun a b c -> func.Invoke(a,b,c)
    static member internal ToFSharpFunc<'a,'b,'c,'d,'e> (func:Func<'a,'b,'c,'d,'e>) = fun a b c d -> func.Invoke(a,b,c,d)
    static member internal ToFSharpFunc<'a,'b,'c,'d,'e,'f> (func:Func<'a,'b,'c,'d,'e,'f>) = fun a b c d e -> func.Invoke(a,b,c,d,e)

  // Action -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc1<'TArg> (action:Action<'TArg>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc2<'TArg1, 'TArg2> (action:Action<'TArg1, 'TArg2>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc3<'TArg1, 'TArg2, 'TArg3> (action:Action<'TArg1, 'TArg2, 'TArg3>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4> (action:Action<'TArg1, 'TArg2, 'TArg3, 'TArg4>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5> (action:Action<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5>) = 
    action |> CSharpFunc.ToFSharpFunc

  // Expression<Action> -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc1<'TArg> (eaction:Expression<Action<'TArg>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc2<'TArg1, 'TArg2> (eaction:Expression<Action<'TArg1, 'TArg2>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc3<'TArg1, 'TArg2, 'TArg3> (eaction:Expression<Action<'TArg1, 'TArg2, 'TArg3>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4> (eaction:Expression<Action<'TArg1, 'TArg2, 'TArg3, 'TArg4>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5> (eaction:Expression<Action<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  // Func -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc1<'TArg, 'TResult> (func:Func<'TArg, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc2<'TArg1, 'TArg2, 'TResult> (func:Func<'TArg1, 'TArg2, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc3<'TArg1, 'TArg2, 'TArg3, 'TResult> (func:Func<'TArg1, 'TArg2, 'TArg3, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult> (func:Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc 

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult> (func:Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc 

  // Expression<Func> -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc1<'TArg,'TResult> (efunc:Expression<Func<'TArg, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc 

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc2<'TArg1, 'TArg2 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc3<'TArg1, 'TArg2, 'TArg3 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TArg3, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  // FSharpFunc -> Func
  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc1<'TArg, 'TResult,'c when 'c :> FSharpFunc<'TArg, 'TResult>> 
    (x: 'c ): ('TArg -> 'TResult) = 
    x.Invoke   

  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc2<'TArg1, 'TArg2, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, 'TResult>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TResult) = 
    fun a b -> x.Invoke(a).Invoke(b)

  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc3<'TArg1, 'TArg2, 'TArg3, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, FSharpFunc<'TArg3, 'TResult>>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TArg3 -> 'TResult) = 
    fun a b c -> x.Invoke(a).Invoke(b).Invoke(c)

  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, FSharpFunc<'TArg3, FSharpFunc<'TArg4, 'TResult>>>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TArg3 -> 'TArg4 -> 'TResult) = 
    fun a b c d -> x.Invoke(a).Invoke(b).Invoke(c).Invoke(d)
 
  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, FSharpFunc<'TArg3, FSharpFunc<'TArg4, FSharpFunc<'TArg5, 'TResult>>>>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TArg3 -> 'TArg4 -> 'TArg5 -> 'TResult) = 
    fun a b c d e -> x.Invoke(a).Invoke(b).Invoke(c).Invoke(d).Invoke(e)

module Sample =

  let a1 a b = a b 
               b |> ignore
  let a2 a b c = a b c
                 c |> ignore
  let a3 a b c d = a b c d
                   d |> ignore
  let a4 a b c d e = a b c d e
                     e |> ignore
  let a5 a b c d e f = a b c d e f
                       f |> ignore

  let t1 a b = a b
  let t2 a b c = a b c
  let t3 a b c d = a b c d
  let t4 a b c d e = a b c d e
  let t5 a b c d e f = a b c d e f


まだまだ不足しているものがありますが、とりあえずということで。



一応、テスト駆動(TDD)でつくってみました

VS2010のテストで、C#からF#のクロージャを受け取る関数を呼び出して見ましょう。
拡張メソッドToFSharpFunc()だけでF#のクロージャに変換できるので、こりゃあ便利ですねー。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq.Expressions;
using Microsoft.FSharp.Core;

namespace TestProject1
{
    /// <summary>
    /// UnitTest1 の概要の説明
    /// </summary>
    [TestClass]
    public class UnitTest1
    {
        public UnitTest1() {}

        #region Action
        [TestMethod]
        public void a1()
        {
            var i = 0;
            Action<int> a = x => 
            {
                Console.WriteLine(x);
                i = 1;
            };

            Sample.a1(a.ToFSharpFunc(),3);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a2()
        {
            var i = 0;
            Action<int, int> a = (x,y) =>
            {
                Console.WriteLine("{0}:{1}",x,y);
                i = 1;
            };

            Sample.a2(a.ToFSharpFunc(),3,9);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a3()
        {
            var i = 0;
            Action<int, int, int> a = (x, y, z) =>
            {
                Console.WriteLine("{0}:{1}:{2}", x, y, z);
                i = 1;
            };

            Sample.a3(a.ToFSharpFunc(), 3, 9, 27);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a4()
        {
            var i = 0;
            Action<int, int, int, int> a = (x, y, z, b) =>
            {
                Console.WriteLine("{0}:{1}:{2}:{3}", x, y, z, b);
                i = 1;
            };

            Sample.a4(a.ToFSharpFunc(), 3, 9, 27, 12);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a5()
        {
            var i = 0;
            Action<int, int, int, int, int> a = (x, y, z, b, c) =>
            {
                Console.WriteLine("{0}:{1}:{2}:{3}:{4}", x, y, z, b, c);
                i = 1;
            };

            Sample.a5(a.ToFSharpFunc(), 3, 9, 27, 12,15);
            Assert.AreEqual(i, 1);
        }
        #endregion

        #region ExpressionAction
        [TestMethod]
        public void a1e()
        {
            Expression<Action<int>> a = x => 
                Console.WriteLine(x);
            Sample.a1(a.ToFSharpFunc(), 3);
        }

        [TestMethod]
        public void a2e()
        {
            Expression<Action<int, int>> a = (x, y) => 
                Console.WriteLine("{0}:{1}", x, y);
            Sample.a2(a.ToFSharpFunc(), 3, 9);
        }

        [TestMethod]
        public void a3e()
        {
            Expression<Action<int, int, int>> a = (x, y, z) =>
                Console.WriteLine("{0}:{1}:{2}", x, y, z);

            Sample.a3(a.ToFSharpFunc(), 3, 9, 27);
        }

        [TestMethod]
        public void a4e()
        {
            Expression<Action<int, int, int, int>> a = (x, y, z, b) =>
                Console.WriteLine("{0}:{1}:{2}:{3}", x, y, z, b);

            Sample.a4(a.ToFSharpFunc(), 3, 9, 27, 12);
        }

        [TestMethod]
        public void a5e()
        {
            Expression<Action<int, int, int, int, int>> a = (x, y, z, b, c) =>
                Console.WriteLine("{0}:{1}:{2}:{3}:{4}", x, y, z, b, c);

            Sample.a5(a.ToFSharpFunc(), 3, 9, 27, 12, 15);
        }
        #endregion

        #region Func
        [TestMethod]
        public void t1()
        {
            Func<int, int> f = x => x * 2;
            var result = Sample.t1(f.ToFSharpFunc(), 3);
            Assert.AreEqual(result,6);
        }

        [TestMethod]
        public void t2()
        {
            Func<int, int, int> f = (x, y) => x * 2 + y;
            var result = Sample.t2(f.ToFSharpFunc(), 3, 5);
            Assert.AreEqual(result, 11);
        }

        [TestMethod]
        public void t3()
        {
            Func<int, int, int, int> f = (x, y, z) => (x * 2 + y) / z;
            var result = Sample.t3(f.ToFSharpFunc(), 3, 6, 4);
            Assert.AreEqual(result, 3);
        }

        [TestMethod]
        public void t4()
        {
            Func<int, int, int, int, int> f = (x, y, z, a) => (x * 2 + y / z) % a;
            var result = Sample.t4(f.ToFSharpFunc(), 10, 8, 2, 5);
            Assert.AreEqual(result, 4);
        }

        [TestMethod]
        public void t5()
        {
            Func<int, int, int, int, int, int> f = (x, y, z, a, b) => (x + y + z + a) / b;
            var result = Sample.t5(f.ToFSharpFunc(), 3, 6, 4, 5, 2);
            Assert.AreEqual(result, 9);
        }
        #endregion

        #region ExpressionFunc 
        [TestMethod]
        public void t1e()
        {
            Expression<Func<int, int>> f = x => x * 2;
            var result = Sample.t1(f.ToFSharpFunc(), 3);
            Assert.AreEqual(result, 6);
        }


        [TestMethod]
        public void t2e()
        {
            Expression<Func<int, int, int>> f = (x, y) => x * 2 + y;
            var result = Sample.t2(f.ToFSharpFunc(), 3, 5);
            Assert.AreEqual(result, 11);
        }

        [TestMethod]
        public void t3e()
        {
            Expression<Func<int, int, int, int>> f = (x, y, z) => (x * 2 + y) / z;
            var result = Sample.t3(f.ToFSharpFunc(), 3, 6, 4);
            Assert.AreEqual(result, 3);
        }

        [TestMethod]
        public void t4e()
        {
            Expression<Func<int, int, int, int, int>> f = (x, y, z, a) => (x * 2 + y / z) % a;
            var result = Sample.t4(f.ToFSharpFunc(), 10, 8, 2, 5);
            Assert.AreEqual(result, 4);
        }

        [TestMethod]
        public void t5e()
        {
            Expression<Func<int, int, int, int, int, int>> f = (x, y, z, a, b) => (x + y + z + a) / b;
            var result = Sample.t5(f.ToFSharpFunc(), 3, 6, 4, 5, 2);
            Assert.AreEqual(result, 9);
        }
        #endregion

    }
}



ということで、今年はF#とC#の二刀流で頑張っていきまーす。