バインド
撃ち終わると、バインドってのも解けちゃうんだね。
C# でバインドするアレなコードを書いてみました。やっぱり T4 Template の力を借りてます。でも T4 Template ももうだめですね。n = 10 とかにするとひどいことに・・・あ、やめたほうがいいですよ。メモリ使用量とかぐいんぐいんあがっていきますし*1。
使い方はこんな感じ。
Func<int, int, int, int> hoge = (a, b, c) => a - b / c; Console.WriteLine(hoge.Bind(100, 20, 2)()); // 90 Console.WriteLine(hoge.Bind(PH._1, 20, PH._2)(100, 2)); // 90 Console.WriteLine(hoge.Bind(PH._2, 20, PH._1)(2, 100)); // 90 Console.WriteLine(hoge.Bind(PH._1, PH._2, PH._3)(100, 20, 2)); // 90 Console.WriteLine(hoge.Bind(PH._1, PH._3, PH._2)(100, 2, 20)); // 90 Console.WriteLine(hoge.Bind(PH._2, PH._1, PH._3)(20, 100, 2)); // 90 Console.WriteLine(hoge.Bind(PH._2, PH._3, PH._1)(2, 100, 20)); // 90 Console.WriteLine(hoge.Bind(PH._3, PH._1, PH._2)(20, 2, 100)); // 90 Console.WriteLine(hoge.Bind(PH._3, PH._2, PH._1)(2, 20, 100)); // 90 // 以下コンパイルエラー //var err = hoge.Bind(100, 20, PH._2); // PH._1がないのでPH._2は使えない //var err = hoge.Bind(PH._1, PH._2, PH._4); // 3引数なのでPH._4は使えない
PH は PlaceHolder のつもりです。
以下、これを実現するコードです。
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#v3.5" debug="true" hostSpecific="true" #> <#@ output extension=".cs" encoding="UTF-8" #> <#@ Assembly Name="System.dll" #> <#@ Assembly Name="System.Core.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Collections.Generic" #> <# int n = 5; #> using System; namespace CommonsSharp.Lang { using PlaceHolderImpl; namespace PlaceHolderImpl { <# for (int i = 0; i < n; i++) { var className = string.Format("PH{0}", i + 1); #> /// <summary> /// <#= i + 1 #>つ目の引数を表すプレースホルダーです。 /// </summary> public sealed class <#= className #> { <#= className #>() {} internal static readonly <#= className #> Instance = new <#= className #>(); } <# } #> } /// <summary> /// プレースホルダーを保持するクラスです。 /// </summary> public static class PH { <# for (int i = 0; i < n; i++) { var className = string.Format("PH{0}", i + 1); #> /// <summary> /// <#= i + 1 #>つ目の引数を表すプレースホルダーです。 /// </summary> public static readonly <#= className #> _<#= i + 1 #> = <#= className #>.Instance; <# } #> } public static class FuncExtension { <# for (int i = 1; i <= n; i++) { var range = Enumerable.Range(1, i); var actualArgs = string.Join(", ", range.Select(j => "arg" + j).ToArray()); var inputTypes = string.Join(", ", range.Select(j => "TArg" + j).Concat(new[] { "TResult" }).ToArray()); foreach (var at in ArgTemplates(i)) { #> /// <summary> /// 関数の引数をバインド(固定)した関数を返します。 /// </summary> public static Func<<#= RetTypes(at) #>> Bind<<#= inputTypes #>>(this Func<<#= inputTypes #>> self, <#= Args(at) #>) { return (<#= LambdaArgs(at) #>) => self(<#= actualArgs #>); } <# } } #> } } <#+ public static IEnumerable<string> ArgTemplates(int crnt) { return CrossJoin(crnt).Where(arg => IsValidSequence(MakeUniqSortedSeq(arg))); } // cross-joinされたコレクションを生成 private static IEnumerable<string> CrossJoin(int argsCount) { // PH0, PH1, ..., PHN | N=argsCount var phSeq = Enumerable.Range(0, argsCount + 1).Select(j => "PH" + j); var args = phSeq.Select(e => e == "PH0" ? "TArg1" : e); // 引数の個数分組み合わせる for (int i = 2; i <= argsCount; i++) { var tmp = new List<string>(); // cross-joinの本体 foreach (var arg in args) { foreach (var ph in phSeq.Select(ph => ph == "PH0" ? "TArg" + i : ph)) { // 同じプレースホルダーは複数回使わない if (arg.Contains(ph) == false) tmp.Add(arg + "," + ph); } } args = tmp; } return args; } // TArgNを0と、PHNをNとみなして重複なしのソートされたシーケンスを生成 private static int[] MakeUniqSortedSeq(string arg) { return arg.Split(',') .Select(a => a.StartsWith("TArg") ? 0 : (a[a.Length - 1] - '0')) .Distinct() .OrderBy(i => i) .ToArray(); } // 1ずつ増加しているかをチェック private static bool IsValidSequence(int[] seq) { for (int i = 1; i < seq.Length; i++) { if (seq[i - 1] + 1 != seq[i]) return false; } return true; } // cross-joinで生成されたコレクションの要素を戻り値のFuncの型パラメータに変換 public static string RetTypes(string seed) { var elems = seed.Split(','); var retTypes = new string[elems.Count(e => e.StartsWith("PH"))]; for (int i = 1; i <= retTypes.Length; i++) { var pos = IndexOf(elems, "PH" + i) + 1; retTypes[i - 1] = "TArg" + pos; } return string.Join(", ", retTypes.Concat(new[] { "TResult" }).ToArray()); } // cross-joinで生成されたコレクションの要素をラムダ式の引数に変換 public static string LambdaArgs(string seed) { var elems = seed.Split(','); var retTypes = new string[elems.Count(e => e.StartsWith("PH"))]; for (int i = 1; i <= retTypes.Length; i++) { var pos = IndexOf(elems, "PH" + i) + 1; retTypes[i - 1] = "arg" + pos; } return string.Join(", ", retTypes.ToArray()); } // cross-joinで生成されたコレクションの要素をメソッドの引数に変換 public static string Args(string seed) { return string.Join( ", ", seed.Split(',') .Select(arg => arg + " " + (arg.StartsWith("T") ? arg.Substring(1) : arg).ToLower()).ToArray()); } private static int IndexOf(IEnumerable<string> es, string str) { int i = -1; foreach (var e in es) { if (e == str) return i + 1; i++; } return i; } #>
本当は hoge.Bind(PH._1, PH._1, PH._1) とかも実現したいんだけど、行数がひどいことになりそうなので考えてません。今の状態でも 3,000 行超えちゃうので・・・
*1:やるとしたら実装を見直したほうがいいです