myugaruの色々構想中・・・!

「C#」「画像処理」「XNA未対応PCでゲームIDE作りの無謀な野望」

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  98  : 記事  0  : コメント  2320  : トラックバック  59

ニュース

myugaru
仕事(昔)=ヲタク系プログラマー~マスコミ系サポートデスク
仕事(今)=電子機器系サービス業
趣味a=パズルゲーム全般、シューティングは主に見学
趣味b=画像処理関係の勉強
趣味c=プログラミング言語の勉強
趣味d=アキバ系ヲタク
趣味e=芸能アイドル系ヲタク
d,e色の強いもう一つのブログ
最新目標=シューティングゲームを作る

わんくまりんく

わんくま同盟blog C#,VB.NET掲示板

ぶろぐつーる

あわせて読みたい

はてなりんぐ

書庫

日記カテゴリ

ギャラリ

お友達

リンク

コルーチンの話を進める上でどうしてもC#のyieldを理解してもらわなくてはいけません。

凪瀬さんも少し誤解されているように感じました。

ここで少し解説したいと思います。


●実は何も返してないyield return
ご存じなければ驚くかもしれませんがyield returnは正確には何も返していません
まずMoveNextの返値ではありません。
MoveNextの返値はMoveNextの成功可否をboolで返します。
つまり列挙が継続可能かを返します。
ではyield returnは何を返しているかというと、
IEnumeratorインターフェースのCurrentプロパティの内容を更新しています。
言語構文上returnという単語を用いているだけなのです。
なので「何か返している」という誤解が生じているように思います。

using System;
using System.Collections;
namespace CoRoutine
{
    static class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            IEnumerator e = TaskA();
            for (int i = 0; i < 10; i++) {
                bool b = e.MoveNext();
                Console.WriteLine(e.Current);
            }
        }
        static IEnumerator TaskA()
        {
            while (true) {
                for (int i = 1; i <= 3; i++) {
                    yield return i;
                }
            }
        }
    }
}



●Currentで表現できる状態変化
具体的なゲームの話をしましょう。
「男の子がまばたきしている」
ことを考えます。
Boyには状態を変化させるUpdateメソッドと
現状を画面へ表示するRenderメソッドがあります。
Boy boy = new Boy();
while (boy.生きている間)
{
    boy.Update();
    boy.Render();
}

Updateメソッドの実装は次のようになるでしょう。
3項演算を使えばもっとシンプル化も可能です。

void Update()
{
    if (this.eye == CLOSE)
       this.eye = OPEN;
    else
       this.eye = CLOSE;
}

ここでは目というプロパティに値を代入することで
状態変化を表現しています。
これはyield構文を使うとこのように書けるでしょう。

class Boy
{
    IEnumerator enumerator;
    public Boy()
    {
        enumerator = GetEnumerator();
    }
    void Update()
    {
        enumerator.MoveNext();
        eye = enumerator.Current;
    }
    IEnumerator GetEnumerator()
    {
        while (true) {
            yield return OPEN;
            yield return CLOSE;
        }
    }
}


●Currentでは表現できないケース
さて別の実装を考えます。
「女の子が携帯メールを打ちながら笑いながら歩いている」
ことを考えます
GirlはBoyと同じく状態を変化させるUpdateメソッドと
現状を画面へ表示するRenderメソッドがあります。
メインループはBoyと同じなので省略します。
yieldを使わない場合、今回のUpdateメソッドはこのように
なっているとします。

void Update()
{
    // メール内容を追加する
    string s = GetNextMailSentence();
    this.phone.Add(s);
    // 歩く
    MoveStep();
}

状態変化はすべてメソッドによって実装されています。
この場合、反論はあるかも知れませんが私はyieldの実装はこのようにするのを理想と考えています。

class Girl
{
    IEnumerator enumerator;
    public Girl()
    {
        enumerator = GetEnumerator();
    }
    void Update()
    {
        enumerator.MoveNext();
    }
    IEnumerator GetEnumerator()
    {
        while (true) {
            // メール内容を追加する
            string s = GetNextMailSentence();
            this.phone.Add(s);
            // 歩く
            MoveStep();
            yield return 0;
        }
    }
}


●私の見解
状態変化が1つのプロパティの代入による場合、
yield returnの返値としてそれを表現することができます。
しかし現実には
・複数のプロパティによって表現された状態
・代入で表現できない状態変化
などは多くありますので一般論的にみるとyield returnによる一つの値の上書きには意味を持たせる必然性は全くないと私は考えます。

むしろyieldを思想として列挙を考えていない別言語のyieldになぞらえて使用しているのである、という意思表示のためにもコルーチンとしてのyieldは無意味な値を返すべきだと考えます。
それはコルーチン向けの言語で返値が存在していない理由の一つだとこじつけられそうにも思います。

投稿日時 : 2008年3月10日 20:26

コメント

# re: C#のyieldに対する誤解と私の見解 2008/03/10 22:13 凪瀬
あぁ、なるほど。
微妙に動きを誤解していました。

yield return の値がMoveNext()の戻り値だと勘違いしていました。
MoveNext()を継続条件、Currentプロパティで値を取得するIteratorパターンなのですね。
自分の思想だとCurrentプロパティが状態を表すオブジェクトであるべき、というところですね。
yield returnへの不理解の分の修正はあるにせよ、主張はあまり変わらないな。

個人的にはメソッドの引数でのoutとかも嫌なのですよね。
return用にわざわざクラスや構造体を定義するのが面倒という人は多いけども。

ところで、C#ってVoidを表す定数ってないのですか?
Javaだったらジェネリクスを使って戻りがvoidであることを表すのに
http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Void.html
を用いるところですね。
IEnumerator<Void>ってな感じでジェネリクスの型パラメータに使うのですが。

# re: C#のyieldに対する誤解と私の見解 2008/03/11 0:08 myugaru
To 凪瀬さん
Currentを状態表すにしたとします。

1.値型であった場合。
returnのたびに現状のインスタンスのクローンを作って丸々上書きにしますか?Currentへの「代入」オペレーションってのはオーバーライドが不可能ですので、もしおっしゃる実装ならそういう風にするしかありません。

2.参照型であった場合
この場合は代入前も代入後も同じインスタンスの参照です。代入には全く意味がありません。

なのでこれは理想論では凪瀬さんのおっしゃることに賛成ですが、
「現実のC#のyieldの実装」という土俵での話では凪瀬さんのおっしゃっている話はNoとしか言いようがありません。
なぜNoしか言えない状況になっているかといえばそれは全て、

「C#のyieldはそんなことに使うために設計されていない。」

で話が済みます。C#のyieldはあくまで「列挙子」ですから、これはもうどうしようもありません。まあ拡張した私が悪いんです。C#言語設計者には罪はございません(笑

>C#ってVoidを表す定数ってないのですか?
残念ながらありませんねえ。


以上です。コメントありがとうございます。



# re: C#のyieldに対する誤解と私の見解 2008/03/11 1:37 myugaru
To 凪瀬さん

すみません、凪瀬さんもCurrent状態維持は無理だなっておっしゃってるんですね。読み取れていませんでした。
また色々とアドバイスぜひともお願いします。
ありがとうございました。

# re: C#のyieldに対する誤解と私の見解 2008/03/11 1:39 凪瀬
なるほど。なるほど。
あくまでIEnumeratorは汎用のコルーチンではなく、イテレータなのだと。

> 2.参照型であった場合
> この場合は代入前も代入後も同じインスタンスの参照です。代入には全く意味がありません。

私なら0をreturnするよりこちらを選ぶかな。
この実装であればちゃんとイテレータとして扱うことができるのですから。
イテレータであることを捨てずにいられるなら、そっちのほうがいい。

# re: C#のyieldに対する誤解と私の見解 2008/03/11 1:41 凪瀬
おや?w
いろいろと行き違いがありますねw
意見が違うということは、そこに何かが潜んでいるということです。
こういう議論から面白いアイデアがでてくるかもしれません。
とても勉強になります。

# re: C#のyieldに対する誤解と私の見解 2008/03/11 8:20 myugaru
To 凪瀬さん
>こういう議論から面白いアイデアがでてくるかもしれません。
心に染み入る言葉です。
期待に添えるかどうか不安いっぱいですが色々がんばります。
ありがとうございます。

# re: C#のyieldに対する誤解と私の見解 2008/03/11 10:15 siokoshou
>Currentでは表現できないケース
の部分、ここまでやって大丈夫かなぁと心配になります。
コルーチン(正確にはyieldは半コルーチン)を複雑なことに使うと、スパゲティコードにならないかなぁと。制御が上から下にまっすぐ進まなくなるので、難解なコードになったりしません?順番の制御が難しくなりそうな気が…。構造化導入以前のようなコードになったりとか心配です(見たことないですけど(^^;)。
あと、凪瀬さんと同じところも心配していて、Currentで状態を返してそれを見るほうがグローバル変数的な使い方より局所的になっていいんじゃないかなぁと思います。
yieldの問題というより、グローバル変数やスパゲティな構成を作ろうとしていないか、慎重に検討したほうがよい気がします。深く考えないで書いてるので(スミマセン(^^;)、心配しすぎなだけかもしれませんが…。


# re: C#のyieldに対する誤解と私の見解 2008/03/11 10:29 siokoshou
連投失礼します。
>言語構文上returnという単語を用いているだけなのです。
この部分、yield は Current で値を返していると考えたほうが自然に理解できる構文だと思います。
http://d.hatena.ne.jp/siokoshou/20070829#p1
kinabaさんの言葉ですが、yield は「内部イテレータ風に外部イテレータを書ける構文」です。

内部イテレータの
for ( int i = 0; i < 10; i++ )
 func( i );

for ( int i = 0; i < 10; i++ )
 yield return func( i );
と外部イテレータに変身してしまうと。

それはそれとして。状態を表すクラスを用意してそれを返すのはどうでしょう?


# re: C#のyieldに対する誤解と私の見解 2008/03/11 10:58 凪瀬
> コルーチン(正確にはyieldは半コルーチン)を複雑なことに使うと、スパゲティコードにならないかなぁと。

このあたりはやってみないと評価しにくいかも。
キャラクタの動作処理ぐらいであれば、適時yield returnする作りでもそれほど繁雑にならない気もする。
実践ノウハウに興味津々ですね。

> 状態を表すクラスを用意してそれを返すのはどうでしょう?

そこで話題がループする、とw
2008/03/11 0:08 の myugaruさんの発言がそれに対する見解ではないですかね。
私の見解は、同じ参照でもいいから状態を表すオブジェクトの参照を返す設計がよい、ということで。

# re: C#のyieldに対する誤解と私の見解 2008/03/11 11:54 myugaru
To siokoshouさん
すみません、すべては例題が悪いんです。もうしわけありません。
現実のゲームをよくご覧ください。

ボス移動タスク
Loop:
3秒間画面中央に位置する。
その後右端まで10秒かけて移動。
4秒停止。
その後20秒かけて左端へ移動。
4秒停止。
Loopへ戻る。

これは

ボス移動タスク
while(true)
 for(i=0;i<3秒;i++) yield;
 for(i=0;i<10秒;i++) 右移動; yield;
 for(i=0;i<4秒;i++) yield;
 for(i=0;i<20秒;i++) 左移動; yield;
 for(i=0;i<4秒;i++) yield;
}

制御は日本語で先に書いたとおり見た目どおりにまっすぐ進んでおります。
これ以上にシンプル且つ見やすいコードは無いと考えています。
いかがでしょう?
なかなか伝わり辛くて申し訳なく思っております。
よろしくお願いいたします。

To 凪瀬さん
フォローありがとうございます。
なかなかコルーチンは理解してもらいにくい概念です。
Windowsプログラミング黎明期にイベント駆動の関数をみて「呼ばれていない関数があるじゃないか、ちゃんと見直せ!」って上司に怒られたことを思い出してしまいましたw


# re: C#のyieldに対する誤解と私の見解 2008/03/11 13:56 凪瀬
概念としてはスレッドのほうが分かりやすいぐらいかもw
制御はコルーチンの方が断然楽だと思いますけども。

そういえば、以前かつのりさんがJavaのThreadでコルーチンの実相を試みていたなぁ。
http://blogs.wankuma.com/kacchan6/archive/2007/09/19/97005.aspx

# コルーチンのyieldはDoEventsです 2008/03/11 14:35 myugaruの色々構想中・・・!
コルーチンのyieldはDoEventsです

# re: C#のyieldに対する誤解と私の見解 2008/03/11 14:39 siokoshou
んー、ちょっと言葉足らずでした。
ボスのコルーチンだけ見ればまっすぐですが、自機のコルーチン、ボスのコルーチン、弾のコルーチンとたくさんのコルーチンが動くんですよね、きっと?
そうなったときに、それぞれのコルーチンが複雑なことをしているとスパゲティにならないかなぁと。
でも、タスクシステムってそういうものだろうから、心配するほどのことでもないのかなぁ。


# re: C#のyieldに対する誤解と私の見解 2008/03/11 14:54 myugaru
To siokoshouさん
コメントありがとうございます。
私なりにがんばって説明させていただきました。
お時間ございましたらまたご意見いただければと思います。

# コルーチンのyieldはDoEventsです 2008/03/11 14:58 myugaruの色々構想中・・・!
コルーチンのyieldはDoEventsです

# 型を継承する以上はis-aであるべき 2008/03/11 21:33 凪瀬 Blog
型を継承する以上はis-aであるべき

# タスクシステムにコルーチンを組み込むには 2008/03/12 23:31 凪瀬 Blog
タスクシステムにコルーチンを組み込むには

Post Feedback

タイトル
名前
Url:
コメント