『みんながUnityでLINQを使う未来』を夢見て


はじめに

今すぐじゃなくても、UnityでLINQが問題なく使える未来が来た時に備えて、LINQ勉強しませんか!? というお話です。
この投稿が、UnityユーザーでLINQを知らない、またはLINQを使っていない方が、LINQを勉強し始め、使い始めるきっかけとなれば嬉しいです。

言いたいこと

Unityゲームエンジンは、C#、UnityScript(JavaScript)、Booで開発ができます。その言語の中で最もメジャーなのはC#ですね。C#の言語機能であるLINQをご存知でしょうか?LINQを使うことで配列、リスト、ディクショナリなどを扱うコードは短くなり、そして読みやすくなります。(※この投稿ではLINQは、LINQ to Objectsのことさせていただきます)

ところがUnityゲームエンジンでのLINQの利用は、iOSで特定の条件を満たすと実行時に例外が発生するなど問題があります。そのため、「LINQの利用を避ける」という開発チームがあるかもしれません。また、Unityゲームエンジンはプログラマだけでなく、ノンプログラマの方や初学者も方も利用します。そのためか、XamarinやWindows StoreアプリなどC#が使われる他のコミュニティに比べて、UnityコミュニティではLINQを利用している人の割合が小さい印象です。

「UnityでもLINQを使いたい」、「iOSで例外発生しないLINQ互換ライブラリが必要だ」そんな思いから、まだベータ版ではありあますが、UniLinqというライブラリを作成し、githubで公開しました。(UniLinqについては、こちらの「やっぱりUnityでもC#なんだからLINQが使いたい!」もご覧下さい)

Unity5.xでLINQの不具合が修正され、「LINQを使うべき」という雰囲気になればいいなと思っています。いつかUnityでも皆が平和にLINQを使える未来が来るのを夢見ています。

さて、そんな未来が来たと仮定しましょう。iOSでも問題が解消され、ノンプログラマだけどコーディングもする人や初学者を除き、ほとんどの人がLINQをバリバリ使っています。

そんな中、LINQを使わない一部のプログラマの方はどうなってしまうでしょう。2014年話題になった「staticおじさん」ではありませんが、「for文おじさん」と呼ばれてしまうかもしれません。またそれだけでなく、他の人がLINQを使って書いたコードが読めなくて、困るということもあるかもしれません。

できれば今すぐLINQを使って頂きたいですし、UniLinqも試していただきたいです。しかし、「そんなバグがあるもの使えるか!」や「どこの馬の骨が作ったか分からんモンなど試せるか!!!」などのご意見も分かります。

今すぐとは言いません。ですが、いつか来る未来に備えて今からLINQを勉強し始めてはいかがでしょうか?

さて、この投稿では先に言いたいことをここで全て言ってしまいました。
ですが、良かったら続きも読んでください。特に「LINQって何?」という方や、「それでもLINQはやらないぞ」という方は。

LINQって何が嬉しいの?

簡単にですが、LINQ to Objectsのメリットを紹介します。

前提として、「コードの簡潔さと読みやすさ」って大切ですよね

面白いゲームや売れるゲームを作るために、大事なこと・大切なことはたくさんあると思います。

その一つに「コードの簡潔さと読みやすさ」もあると思います。

ゲーム開発では、仕様が変わることが日常茶飯事だと思います。「長ったらしくて汚く何をしているか分かりづらいコード」と「簡潔で読みやすいコード」だったら、仕様変更がすぐにできるのはどちらでしょうか?また汚いコードはバグが発生しやすく、その原因を特定するのも困難になることが多いです。

そして、モバイルプラットフォームでのゲーム開発が増えた昨今、アップデートによる仕様追加も増えました。「今、正しく動いていればコードが汚くても良いや、リリースしよう」という考えを大きく変える必要が出てきたと思います。

「簡潔で読みやすいコード」この重要性に賛成していただけたでしょうか?

LINQを使うとコードが短くなる

LINQを使うことで、配列、リスト、ディクショナリなどを扱うコードが非常に簡潔になります。(正確にはIEnumerable<T>を実装しているものを扱うコード)

Player.cs
public class Player
{
    public int HP { get; set; }
    /* 他にもいろいろ */
}

このようなPlayerクラスがあります。今、List<Player>型のplayerListがあるとします。このplayerListの中の要素で、HPが0以下の要素を数えます。RPGなどのゲームでHPが0、つまり死んでいるキャラクターの数えることを想像してください。

まずはLINQを使わず、foreach文とif文で書いてみましょう。

LINQを使わず、foreach文とif文で
int deadPlayerCount = 0;
foreach (Player player in playerList) {
    if(player.HP <= 0) {
        deadPlayerCount++;
    }
}

大した長さではありませんが、6行で書けました。

これをLINQを使って書き換えます。

LINQを使って
int deadPlayerCount = playerList.Count (player => player.HP <= 0);

たったの1行になってしまいました。

このように、配列やリストなどを扱うコードは、foreach文やif文で書くのに比べて、LINQを使うことで非常に短くなります。

LINQを使うとコードが読みやすくなる

短くなるだけでなく、LINQを使うことでそのコードが『何をしたいのか』が読みやすくなります。

先ほどのコードは『playerListの要素中、HPが0以下(死んでいる)の要素(Player)を数える』をするコードでした。ではこのコードを書く側ではなく、読む側の立場になって考えてみます。

先ほどのforech文とif文版のコードと、LINQ版のコードを「何がしたいか調べる人」の気持ちになって読み比べます。

LINQを使わず、foreach文とif文で(再掲)
int deadPlayerCount = 0;
foreach (Player player in playerList) {
    if(player.HP <= 0) {
        deadPlayerCount++;
    }
}

このコードを読む際、頭の中では次のように読むのではないでしょうか?

int型のdeadPlayerCountを0で初期化
foreach文でplayerListをまわす
もし要素のplayerのHPが0以下ならば
deadPlayerCountをインクリメント

一度上記のように読んだ後、1回頭の中で変換をすると思います。foreach文・if文で記述された手続きから、処理を把握して、そこからこの6行の目的・やりたいことは何かを変換して、『playerListの要素中、HPが0以下(死んでいる)の要素(Player)を数える』と理解しているのでは無いでしょうか。

さて、次はLINQ版を見てみます。

LINQを使って(再掲)
int deadPlayerCount = playerList.Count (player => player.HP <= 0);

LINQが使える人はどのように上記のLINQで書かれたコードを読むのでしょうか?次の日本語は自然に左から右に読んだ際、「こう読める」というものを書き下したものです。

int型のdeadPlayerを次の値で初期化する。
playerList内の次の条件を満たす要素数。
条件は要素のHPが0以下。

コードの詳しい意味には触れませんが、LINQを理解すればの上記のように読めます。foreach文・if文で書かれたものに比べて、『playerListの要素中、HPが0以下(死んでいる)の要素(Player)を数える』にかなり近いと思います。

foreach文・if文で配列やリストなどを扱うコードを書いた際、手続きが明示され、何をどう処理するかははっきりと分かります。ですが、何をしたいかは、1回頭の中で考える必要があります。一方でLINQで書かれたコードは、『何がしたいか』が読み取りやすいコードになります。

けどUntyでは、あまりLINQが利用されてない?

LINQを使うことで配列やリストなどを扱うコードが非常に簡潔にそして読みやすくなります。

XamarinやWindows Storeアプリなど他のC#コミュニティでは、ほとんどの皆さんが使われている印象です。実際Xamarinの勉強会でLT中に挙手で聞いてみたところ、9割5分の人がLINQを知っているとのことでした。(残りは、私のLTを聞かないで夢中で議論されていた方達を含みます)

そんなすばらしいLINQですが、今のところあまりUnityコミュニティでは、そこまで使われていない印象があります。その理由は次の2つではと思っています。

  • iOS実機で特定の条件をみたすと、例外が発生してしまうため
  • Unityの利用者にはノンプログラマの方や初学者の方も多いため

iOSでの問題が大きい...

Unityは、iOSでの実機でいくつか問題を抱えています。いくつかの条件がそろった際、他のプラットフォームでは発生しない、実行時の例外がiOSだと発生してしまうのです。これは一部のLINQメソッドでも発生します。

その原因は、neueccさんがこちらの「Unity + iOSのAOTでの例外の発生パターンと対処法」にまとめられています。

また、どのようなLINQメソッドがどういう条件で例外が発生するか、私が把握している範囲を「Unity+iOSでエラーになるLINQのまとめ」という投稿にまとめています。

iOS・Androidのモバイルプラットフォーム向けのゲームが多い現状、まだLINQを知らない人から見たら、「こんなにエラーになるなら使えない」とか、「便利そうだけれど、例外発生するのは怖い」などの感想を持たれてしまうのも仕方ないと思います。

いろいろな人が使う、Unity!

Unityは様々な人が使っています。ノンプログラマの方や初学者の方も多く、そのような方も実際にコードを書いていると思います。すばらしいですね!

そのような方は、LINQを使うよりまず他に覚えて理解しなくてはいけないことがたくさんあります。

そのような方達もいるため、Windows Storeアプリコミュニティ、Xamarinのコミュニティに比べて、UnityコミュニティでLINQを使っている人の割合は自然と小さくなると思います。

初学者の方、ある程度Unityを使えるようになったら、ぜひC#の言語要素やLINQも勉強してみて下さい!

LINQが当たり前の未来のために!

LINQを使うのは大きなメリットがあります。しかしUnityではiOSで問題があります。また、あまりUnityコミュニティでは使われていない印象です。

まだまだ修行中の身ではありますが、これをちょっとでもなんとかしたいと思いました。LINQが当たり前の未来のためにいくつか行動しました。

ライブラリ作った!

UniLinqというライブラリをつくりました。まだベータ段階ではありますが、Unity+iOSでもLINQが例外発生なしで使えることを目指したLINQ互換クラスライブラリです。githubで公開しました。ぜひ使ってみてください!

何か問題点などありましたら、@RyotaMurohoshiまで教えていただけると嬉しいです。

LTした!

勉強会やイベントでLTやショートセッションを行いました。まだLINQを知らなかったり使っていない方向けに、LINQを知ってもらったり興味を持ってもらうことが目的です。

私も所属している日本Androidの会 Unity部というコミュニティ主催の【年末だよ】Unity お・と・な のLT大会2【ポロりもあるよ】というイベントで、『「LINQ」っていう名前だけでも 覚えて帰ってください!』というタイトルでLTをやりました。そのイベントの参加者の方で、LINQを知っている方は4割を未満でした。このLTでコードが簡潔になり読みやすくなるメリットを伝え、また何度も「LINQ」という名前をお見せしたので、その場の全員に名前は覚えて頂けたのではと思います。

また、プロ生(プログラミング生放送)という勉強会で、『私とUnityとLINQと』と題してショートセッションをやらせていただきました。こちらはマスコットアプリ文化祭の表彰式も同時に行われ、Unityを使われている方もたくさんいました。また、とても優秀な学生さんも多く私も負けていられないと強く思いました。参加者の方に、LINQの何がいいのか、またUnityでの問題点や課題をお話させていただきました。

やはり様々なバックグラウンドを持ちいろいろなレベルの方がいるUnityコミュニティ、そしてUnityユーザーの方向けには、まずLINQという名前とそのメリットだけでも覚えてもらうこと、それを単純で身近な例を使って説明することが大事かなと思いました。2つともLINQを知っている人から見ると、「当たり前だよね」という感じだったと思います。

同人誌に一章書いた!

私も所属している日本Androidの会 Unity部は、コミックマーケット87で「UNIBOOK2」というUnityのチップスを集めた技術同人誌を頒布します。

その「第4章 すぐ使えるオススメLINQメソッド4選」を書かせていただきました。

ある程度Unityを使っている方向けに、

  • Count
  • Any
  • All
  • First

メソッドの使い方の例を簡単に説明し、LINQのメリットを紹介しています。他の章内容盛りだくさんです!よろしくお願いします。

さて、この章は2つのテーマを設定しました。

  • 「LINQ を知らない方にメリットを伝え、興味を持ってもらう」
  • 「なるべく C#の言語要素は必要最小限しか説明しない」

です。二つ目はとても悩みましたがテーマとして採用しました。

LINQを勉強する際、ほとんどの人がそれまで知らなかったC#の言語要素をLINQと同時に学ばないといけない思います。

私は何かを教える際は「いろいろ一気に教えすぎないのが大事」と思っています。

LINQをある程度使えるためには、「デリゲート」、「匿名関数」、「ラムダ式」、「共通デリゲート型」は避けて通れないと思います。ですがこれらを同時に覚えて理解しようとして混乱してしまい、「結局理解できない」、「LINQの何が良いのか分からない」となってしまう人も少なからずいると思います。

今回はあえて、「デリゲート」や「ラムダ式」などの言葉を登場させませんでした。『player => player.HP <= 0は条件を表します、今はそう思ってください。』とし、今回は「LINQ を知らない方にメリットを伝え、興味を持ってもらう」ということにフォーカスしました。

こちらについて、何か意見や感想がありましたら、よろしくお願いします。

それでもLINQを勉強しないんですか?

「今も今後もLINQやらないよ」

そういう方どうしてでしょうか?おそらく何かしら理由があってのことだと思います。

「LINQ、C#とかしか使えないし、他の言語で役に立つの?」

「LINQが使えるのは、C#とかVB.NETとか一部のプログラミング言語だけなんでしょ?他のプログラミング言語で役に立たないなら、勉強しないよ」

そう思われている方、ちょっと待ってください。確かにLINQが使えるプログラミング言語は限られています。ですが、LINQを使うことで培った考え方は無駄にならず、他のプログラミング言語でもバリバリ生かすことができます。LINQとまったく同じAPIは存在しませんが、それと同じようにコレクションを扱えるAPIは他のプログラミング言語でも存在します。

関数オブジェクト、関数リテラル、デリゲート、クロージャ、関数型インターフェース。言い方や仕様はプログラミング言語それぞれです。これらを使って、どう変換するかやどうフィルタリングするかなどを表し、リストやディクショナリなどのコレクションを扱うAPIは、多くのプログラミング言語でも存在します。

C#と比較されることの多いJavaでも、Java8でStream APIが加わりました。JVM言語のScalaやGroovyでは古くから、関数リテラルやクロージャを用いたすばらしコレクションAPIを持っています。また、RubyやPythonでもLINQの様にコレクションを扱えるAPIはあるそうです。

LINQを使うことで得られたコレクションを扱うコードの書き方、そして考え方は無駄にならず、他のプログラミング言語でも十分に生かすことができます。

「で、LINQ知らないと困るの?別にfor文でよくね?」

コレクションを扱う処理はLINQを使わずとも、for文やforeach文、if文を使えば可能です。

ですが、だからと言ってLINQを勉強しなくても良いという分けではありません。仮にみんながLINQを使う未来が来たとしましょう。そうなった時困るのは、LINQを勉強しない人です。

なぜか。それは「サンプルコードがLINQを使い書かれていてサンプルが読めない。また他の人のコードがLINQを使い書かれていて他の人のコードが読めない」ということになるからです。

LINQの対象は配列、リスト、ディクショナリなどのコレクションを扱うコードです。ゲームでもゲーム以外でも、オブジェクトの集まり・コレクションを扱うということは非常に多いと思います。そのコードが「LINQを勉強していないと読めない!」ということになってしまうと思います。

LINQを使わない人は「for文おじさん」と呼ばれる未来

もしUnity5.xで安心してLINQが使える未来が来たとしましょう。もしくは安心してLINQを使える互換ライブラリが登場したとしましょう。(私のUniLinqがそうなれるよう頑張ります)

「安心してLINQが使える未来」が来た時に、それでもLINQを使わない方は「for文おじさん」と呼ばれてしまうかもしれません。

2014年4月、「staticおじさん」という言葉が話題になりました。「オブジェクト指向言語をオブジェクト指向を使わず書け」などなど非常に話題になりましたね。

私は「staticおじさん」の次は「for文おじさん」だと思っています。先に述べたように、for文やif文でコレクションを扱うのでなく、関数オブジェクト(言い方はそれぞれですが)を用いてコレクションを扱うAPIは多くのプログラミング言語に存在します。

それを使わない人は、「コレクションAPIがあるのにコレクションAPIを使わないで、for文とif文で手続きをごりごり書く」、「for文おじさん」と裏で「staticおじさん」のように馬鹿にされてしまうこともあるかもしれません。

最後に

Unityプログラマの方が、今すぐにLINQを勉強しないといけないとは思っていません。ですがなるべく早く勉強した方がいいと思っています。

今すぐでなくてもいい理由は、「まだiOSでLINQのメソッドが特定条件を満たすと例外が発生するから」です。Unity 5.xで解消されることを祈っています。

またUnity 5.xで直らなくても、信頼できるLINQ互換ライブラリが登場したらすぐにでもLINQを勉強すべきだと思います。私のUniLinqも、信頼できるライブラリとなれるように開発を続けたいと思います。

今すぐではなくてもいいですが、なるべく早い方がいいの確かだと思います。
さぁLINQ、未来に備えて勉強しましょう!

ちょっと補足

ちょっと補足です。「for文おじさん」という単語を出しましたが、foreach文を完全に無くした方がいいとは思っていません。

例えば、

nameList.Where (name => name.Length > 5).ToList ().ForEach (name => {
    // nameを使った何らかの処理
})

は無駄にToListする必要なく、次のように書いた方がいいと思います。

foreach(string name in nameList.Where (name => name.Length > 5)){
    // nameを使った何らかの処理
})

LINQで書けるのに無駄なfor文・foreach文・if文は良くないと思います。
ですが、無駄にToListを呼び出し、ForEachメソッドを無理に呼び出すのは良くないと思っています。