kazumalab tech log

流行りとリラックマと嵐が大好きです。技術的ログ。

Unity C#でHaskellのmapっぽいものを実装したお話

かずまです。

最近、すごいHaskell楽しく学ぼう! を読み始めました。

おもしろおかしく書かれているところもあるので読み行ってしまいます。
なので、寝る前に少し読む、みたいなスタイルで。

Haskellの高層関数

Haskellには高層関数と呼ばれるものがあり、マッピング(map)や畳込みなどが例に挙げれらます。
その中でも今回はmapを実装してみたいと思います。

mapとは?

map関数はリストを返す関数で、引数にfの関数を取り、引数2つめのリストの中身にその関数を適用してくれるという優れもの。
例は以下のように書きます。

map (2 *) [1, 2, 3, 4, 5]
-- [2, 4, 6, 8, 10]

Haskellを僕も今回初めて読み書きしましたがこのように書くようです。
(2 *)は関数で引数としてintを一つ受取り2倍するというもの。

初見では何してるの!?これって感じでした。笑
今回はmapという関数を作って、関数とリストを投げて、マッピングして適用させます。

C#でのFunctional的に書く方法

  • Lambda式を使う

C#でもLambda式が使えるのでそれを利用します。
無名関数とも言いますね!

公式ドキュメントはこちら。
Lambda Expressions (C# Programming Guide)

  • 引数でデリゲートを使う

Lambda式を受けるデリゲートは

Func(T, TResult) Delegate (System)

ココらへんを使えば良さそうです。

** Func

これは一つ目のTは引数の型、TResultは返り値の型をGenericで指定するようです。

なんと、この2つを使えばできるはずですね!!

クラスとメソッドの作成

とりあえず、Funcというクラスを作って、staticでListを返すメソッドを用意してみます。

このような感じで書いてみました。
メソッドをGenericにするのはうーん、どうなのかな。
ClassをGenericにするのもいい気がするけど今はこっちでかきました。

使い方

先程のクラスとは別にFunctorTestクラスを用意します。
以下のように記述してみました。

public class FunctorTest : MonoBehaviour {

	private List<int> list = new List<int>(){ 1, 2, 3, 4, 5 };

	private void Start () {
		System.Func<int, int> f =  (int arg) => {
			return arg * 2;
		};

		list = Functor.map (f, list);

		print (list);
	}
}

追記 2/7

Lambda式をまとめるのもありです。

public class FunctorTest : MonoBehaviour {

	private List<int> list = new List<int>(){ 1, 2, 3, 4, 5 };

	private void Start () {

		list = Functor.map ((int arg) => {return arg * 2;}, list);
		print (list);
	}
}

このコードではHaskellと同じように2倍するLambda式を書いて、それをmapの第一引数として渡すだけ!
簡単ですね!ただHaskellと違ってタイプする数は多いのでスマートじゃない...

これぐらいなら許容範囲!


mapはGenericだけど型を指定しなくても良さそう

mapはGenericな型を要求するので実際は

list = Functor.map<int> (f, list);

と指定するのがいいのか、と想定していました。
ところがTestクラスを書いた時にmapの後ろに型の指定を忘れていたのです。

でもコンパイルエラーも出なく、きちんと出力されていたので
自動的に型を認識するのかと感動してしまいました。

f:id:kazumalab:20170130235140p:plain

きちんとint型だと認識していますね。
公式のドキュメントにもきちんとかいてありました。
Generic Methods (C# Programming Guide)

便利だしHaskellに一歩近づいた?笑

出力結果

f:id:kazumalab:20170130235534p:plain

素晴らしい、ちゃんと出てますね!

Stringでも試してみよう!

Listのintが出来たのでstringでも同様に使えるはずです。

	private List<string> slist = new List<string>() { "Hello", "World" };

	private void Start () {

		System.Func<string, string> fs =  (string arg) => {
			return arg + "(ΦωΦ)";
		};

		slist = Functor.map (fs, slist);

		print (slist);
	}

この様にHelloとWorldの後ろに(ΦωΦ)を付け足すスクリプトです。

結果

f:id:kazumalab:20170131003311p:plain

にゃーと猫がでてきましたね。

まとめ

Haskellはまだまだ初心者ですので間違ってる解説などありましたらご指摘ください。
あとこう書いたほうがいいよ〜みたいなのもぜひ!

今回はHaskellっぽくC#で書いてみよう!みたいな感じでしたが、
おもったより便利だと思います。

追記

C# やるなら LINQ を使おう | プログラマーズ雑記帳

LINQを使えば畳み込みも写像も出来るみたい。
次にそのことを書きたいではある。