【Unity】Unity + C# でカリー化【C#】
Unity 独自のクラスを用いて,メソッドをカリー化します。
Method(a, b) → Method(a)(b)
(Unity 2019.4.17f1 Personal)
始めに
『Wikipedia』では,カリー化を次のように説明しています。
カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
1文で簡潔にまとめられていますが,以下の実例にて補足いたします。
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { // int と float をパラメータとして string を返すメソッド string NumberToString(int a, float b) { return $"{a}, {b}"; } // ↓カリー化 // int をパラメータとして Func<float, string> を返すメソッド System.Func<float, string> NumberToString(int a) { // float をパラメータとして string を返すデリゲート return ((float b) => $"{a}, {b}"); } // 呼び出し例 void Start() { // カリー化前の呼び出しかた NumberToString(1, 2.3f); // カリー化後の呼び出しかた NumberToString(1)(2.3f); // int のみ入力した状態も作れる(部分適用) var A = NumberToString(1); // 残った float を入力 A(2.3f); } }
カリー化後は呼び出しかたが変わったり,一部の引数を固定化(部分適用)できたりといった特徴が見られます。
カリー化は主に後者の利点から用いられると思いますが,今回は前者に注目します。
Translate(float, float, float)メソッドに見られるように,Unity ではfloat型の引数でVector3を操作する事があります。
そこで,本稿では「Method(0, 0, 0)(1, 1, 1);」のような記述でVector3を操作するメソッドを作ってみたいと思います。
カリー化前のメソッドを準備
あまり使い道はなさそうですが,「2点の座標の中点を返す」というメソッドを用意します。
Vector3 型パラメータ
目標はfloat型パラメータですが,まずはパラメータ数が少ないVector3型で作ってみます。
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { /// <summary> /// 2つの座標の中点を返す /// </summary> /// <param name="v1">座標1</param> /// <param name="v1">座標2</param> /// <returns>座標1,2の中点</returns> Vector3 Middle(Vector3 v1, Vector3 v2) { // 2つの座標の中点を返す return ((v1 + v2) / 2f); } void Start() { // 2つの座標の中点をログ出力 Debug.Log(Middle(Vector3.zero, Vector3.one)); } }

Vector3.zeroとVector3.oneの中点なので,期待どおりに(0.5, 0.5, 0.5)が出力されました。
次は,パラメータをVector3型ではなくfloat型にしてみましょう。
float 型パラメータ
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { /// <summary> /// 2つの座標の中点を返す /// </summary> /// <param name="x1">座標1のX</param> /// <param name="y1">座標1のY</param> /// <param name="z1">座標1のZ</param> /// <param name="x2">座標2のX</param> /// <param name="y2">座標2のY</param> /// <param name="z2">座標2のZ</param> /// <returns>座標1,2の中点</returns> Vector3 Middle(float x1, float y1, float z1, float x2, float y2, float z2) { // 2つの座標の中点を返す return ((new Vector3((x1 + x2), (y1 + y2), (z1 + z2)) / 2f)); } void Start() { // 2つの座標の中点をログ出力 Debug.Log(Middle(0, 0, 0, 1, 1, 1)); } }
うーん,呼び出しかた(Middle(0, 0, 0, 1, 1, 1))が美しくないですね。
何とか,floatを3つずつに区切りたい所です。
タプル型パラメータ
値を区切る方法として,まずタプルが考えられます。
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { /// <summary> /// 2つの座標の中点を返す /// </summary> /// <param name="v1">座標1</param> /// <param name="v2">座標2</param> /// <returns>座標1,2の中点</returns> Vector3 Middle((float x, float y, float z) v1, (float x, float y, float z) v2) { // 2つの座標の中点を返す return ((new Vector3((v1.x + v2.x), (v1.y + v2.y), (v1.z + v2.z)) / 2f)); } void Start() { // 2つの座標の中点をログ出力 Debug.Log(Middle((0, 0, 0), (1, 1, 1))); } }
(float x, float y, float z)という即席の型を,Vector3に転用しています。
これでも悪くないのですが,かっこ( )やカンマ,が多すぎるように思えます。
そろそろ本題のカリー化に移りますが,例によってまずはVector3型で試してみましょう。
Vector3 型パラメータ(カリー化)
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { /// <summary> /// 2つの座標の中点を返す /// </summary> /// <param name="v1">座標1</param> /// <param name="v1">座標2</param> /// <returns>座標1,2の中点を返すデリゲート</returns> System.Func<Vector3, Vector3> Middle(Vector3 v1) { // 2つ目の座標をパラメータとして1つ目の座標との中点を返すデリゲートを返す return ((Vector3 v2) => (v1 + v2) / 2f); } void Start() { // 2つの座標の中点をログ出力 Debug.Log(Middle(Vector3.zero)(Vector3.one)); } }
冒頭で例示したような,Middle(Vector3.zero)(Vector3.one)という形式で記述できていますね。
次はいよいよ,Middle(0, 0, 0)(1, 1, 1)という呼び出し形式を実現させます。
float 型パラメータ(カリー化)
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { /// <summary> /// 2つの座標の中点を返す /// </summary> /// <param name="x1">ベクトルのX座標</param> /// <param name="y1">ベクトルのY座標</param> /// <param name="z1">ベクトルのZ座標</param> /// <returns>座標1,2の中点を返すデリゲート</returns> System.Func<float, float, float, Vector3> Middle(float x1, float y1, float z1) { // 2つ目の座標をパラメータとして1つ目の座標との中点を返すデリゲートを返す return ((x2, y2, z2) => new Vector3(x1 + x2, y1 + y2, z1 + z2) / 2f); } void Start() { // 2つの座標の中点をログ出力 Debug.Log(Middle(0, 0, 0)(1, 1, 1)); } }
パラメータが多いため難しそうに見えますが,前項のVector3型を用いた物と大差はありません。
new Vector3(0, 0, 0)とnew Vector3(1, 1, 1)の中点なので,期待どおりに(0.5, 0.5, 0.5)が出力されました。
(1枚目と代わり映えしないため,画像は省きます。)
おまけ(部分適用の実例)
冒頭で触れた「部分適用」に関しても,同じメソッドで御説明いたします。
// 他は直前のコードと変わらないため変更部分のみ抜粋 void Start() { // 2つの座標のうち1つ目に原点を指定 var HalfOf = Middle(0, 0, 0); // 2つの座標の中間をログ出力 Debug.Log(HalfOf(1, 1, 1)); }
1つ目の座標が原点であれば“中点”は2つ目の座標の半分に等しいので,1つ目の座標に原点を指定した状態のデリゲートをHalfOfと定義します。
varキーワードで宣言していますが,実際の型はFunc<float, float, float, Vector3>です。
結果は同じなので再び画像は省きますが,期待どおりに(0.5, 0.5, 0.5)が出力されました。
終わりに
カリー化について,2つの利点から解説いたしました。
まだまだ面白い活用法はあると思うので,またカリー化に関する記事が書けると嬉しいですね。
以上,Unity でカリー化を行う例でした。