inohilog

/var/log/inohiro

Monoの奇妙な現象

ご存知の通り、大抵のプログラミング言語では「\t」がTabを挿入するエスケープシーケンスになっていると思いますが、C#でもそうです。「\t」じゃなくて「\t」です。
昨日課題の為に書いたC#のコードで、結果の出力時に「\t」を使って結果を整えて出力させようとしたのですが、Mac OS Xでは「\t」が無視されてしまいました。

CellularAutomataのコード

ルール184(184規則)に基づいたCellular Automata の空間時間図(時間経過とともに、どのように変化しているか)を出力するコードです。

using System;
using System.Collections.Generic;

public class CA
{
	public static bool GetNext( bool prep, bool current, bool next )
	{
		if ( prep ) // 1**
		{
			if ( current ) // 11*
			{
				if ( next ) return true; // 111
				else return false; // 110
			}
			else return true; // 10*
		}
		else // 0**
		{
			if ( current ) // 01*
			{
				if ( next ) return true; // 011
				else return false; //010
			}
			else return false; // 00*
		}
	}
	public static List<Boolean> GetRecord( List<Boolean> list )
	{
		List<Boolean> tmp = new List<Boolean>();

		for ( int i = 0; i < list.Count; i++ )
		{
			if ( i == 0 ) tmp.Add( GetNext( list[list.Count - 1], list[0], list[1] ) );
			else if ( i == list.Count - 1 ) tmp.Add( GetNext( list[i - 1], list[i], list[0] ) );
			else tmp.Add( GetNext( list[i - 1], list[i], list[i + 1] ) );
		}

		return tmp;
	}
	public static void Main( string[] args )
	{
		string val = Console.ReadLine();
		List<Boolean> list = new List<Boolean>();
		CharEnumerator cenum = val.GetEnumerator();

		cenum.MoveNext();

		for ( int i = 0; i < val.Length; ++i )
		{
			if ( cenum.Current.Equals( '0' ) ) list.Add( false );
			else if ( cenum.Current.Equals( '1' ) ) list.Add( true );
			cenum.MoveNext();
		}

		Console.Write( "t = 0:\t" );
		foreach ( var rel in list )
		{
			if ( rel == true ) Console.Write( 1 );
			else Console.Write( 0 );
		}
		Console.WriteLine();

		for ( int i = 0; i < 20; i++ )
		{
			var result = GetRecord( list );
			Console.Write( "t = {0}:\t", i + 1 );
			foreach ( var rel in result )
			{
				if ( rel == true ) Console.Write( 1 );
				else Console.Write( 0 );
			}
			Console.WriteLine();
			list = result;
		}

		Console.ReadKey();
	}
}

実行結果

これを実行すると、任意の「0」と「1」の数字列を受け取ってルール184に則って変化させます。以下はWindowsでのVisual Studioにおける実行例。

1100010111011111110011010101011011111111
t = 0:  1100010111011111110011010101011011111111
t = 1:  1010001110111111101010101010110111111111
t = 2:  0101001101111111010101010101101111111111
t = 3:  1010101011111110101010101011011111111110
t = 4:  0101010111111101010101010110111111111101
t = 5:  1010101111111010101010101101111111111010
t = 6:  0101011111110101010101011011111111110101
t = 7:  1010111111101010101010110111111111101010
t = 8:  0101111111010101010101101111111111010101
t = 9:  1011111110101010101011011111111110101010
t = 10: 0111111101010101010110111111111101010101
t = 11: 1111111010101010101101111111111010101010
t = 12: 1111110101010101011011111111110101010101
t = 13: 1111101010101010110111111111101010101011
t = 14: 1111010101010101101111111111010101010111
t = 15: 1110101010101011011111111110101010101111
t = 16: 1101010101010110111111111101010101011111
t = 17: 1010101010101101111111111010101010111111
t = 18: 0101010101011011111111110101010101111111
t = 19: 1010101010110111111111101010101011111110
t = 20: 0101010101101111111111010101010111111101

今回注目してほしいのは、「t = n: *****」の所です。nが2桁以上の数字になっても***がずれていませんが、これは出力に「\t」を含ませたからです。が、これをMac OS X上のMonoで実行すると、「\t」が無視されて出力されちゃいます。

inohiro-mac:02-081212 inohiro$ gmcs CellularAutomata.cs; mono CellularAutomata.exe 
011100110101110101110101111111
t = 0:10001001111011
t = 0:011100110101110101110101111111
t = 0:111010101011101011101011111110
t = 1:110101010111010111010111111101
t = 2:101010101110101110101111111011
t = 3:010101011101011101011111110111

MonoDevelopで「Unix互換の設定」とかそんなのがあったから、それを利用しないといけないのかな(コンパイルオプションみたいな?)と思ったのですが、そうでもない。Monoで実行するC#ではエスケープシーケンスが違うのかなとも考えたんですが、これも違いそう(それじゃダメでしょ)。
ググってみたところ、ImformIT:Introducing C# > Escaping Charactersにいくつかのエスケープシーケンスの利用方法(単純に記述の仕方ですが)が書かれていて、Monoコンパイラコンパイルした後、実行した結果が載っています。やはり「\t」はTabを挿入するエスケープシーケンスとしては有効なようです。

記事のコード

using System;

class  Hello
{
    public static void Main()
    {
        Console.WriteLine("Single quote: \'");
        Console.WriteLine("Quotation mark: \"");
        Console.WriteLine("Backslash: \\");
        Console.WriteLine("Alert: \a ");
        Console.WriteLine("Backspace: -\b");
        Console.WriteLine("Formfeed: \f");
        Console.WriteLine("Newline: \n");
        Console.WriteLine("Carriage Return: \r");
        Console.WriteLine("Tabulator: before\tafter");
        Console.WriteLine("Tab: \v");
        Console.WriteLine("binary 0: \0");
    }
}

私の環境での実行結果

inohiro-mac:02-081212 inohiro$ mcs hello.cs; mono hello.exe 
Single quote: '
Quotation mark: "
Backslash: \
Alert:  
Backspace: -
Formfeed: 

Newline: 

Carriage Return: 
Tabulator: before	after
Tab: 

binary 0: 

ということで、「\t」でTabが入力できました。なぜ私が課題の為に書いたコードだと、Tabが挿入できないのでしょうか。


一つ考えたのはMonoコンパイラの使い方(?)でした。Monoコンパイラは「mcs」コマンドを用いて利用しますが、これは.NET 1.1をターゲットとしたコンパイラなので、「System.Collections.Generic」名前空間などの.NET 2.0以降の機能をusingディレクティブに追加していたり、ソースコード中で使っていると、

133051080127:02-081212 inohiro$ mcs CellularAutomata.cs
CellularAutomata.cs(32,43): error CS1644: Feature `generics' is not available in Mono mcs compiler. Consider using Mono gmcs compiler instead

といったようなエラーが出ます。.NET 2.0をターゲットとしたコンパイラを用いるときは「gmcs」を使います。今回書いたコードでは「List」などの.NET 2.0からの機能を用いているため、「gmcs」でコンパイルしました。さっきの単純な例をgmcsでコンパイルしたら結果は変わるかな(いや変わらないだろうな)と思いながらコンパイル、実行してみましたが予想通り結果は変わらず。
ということでコンパイラ説はすぐに無くなりました。


この謎は未だに(2008/12/19 00:34)解決していません。先ほど友達と晩ご飯を食べているときに、話したところ「文字コード」が原因ではないかといわれました。でもMacでもWindowsでもUTF-8で書いているはずなので、、、とよく考えるとTerminal.appがEUC-JPだったかもしれないので確認してきます(しかし文字コードが原因なのか...)。
Terminal.appはUTF-8でした。ということでUTF-8で書いてるはずなんですが、あれか、改行コードか。ううむ。


どこかに原因が分かる方はいらっしゃらないかな。

追記

WindowsVisual Studio)側でコンパイルしたバイナリ(「\t」がちゃんと使える)をMac OS X側に持って来て、Monoで実行したらどうかなと思って、やってみた。

inohiro-mac:Desktop inohiro$ mono CellulaAutomata.exe 
01001011101101111001011101001111110
t = 0:01001011101101111001011101001111110
t = 1:00100111011011110100111010101111101
t = 2:10010110110111101010110101011111010
t = 3:01001101101111010101101010111110101

Tabは挿入されませんでした。ということで、これはコンパイラの問題ではなくて、ターミナルにおける「Tab」を表すエスケープシーケンスの違い(のようなもの?)が原因なのかもしれない(「\t」は挿入されてるけど、Terminal.appではそれがTabを表すエスケープシーケンスではない、とか)。
LinuxにMonoを入れて、検証してみるか。あとはiTermとか使ってみるとか。そもそもbashじゃダメなのか。ううむ。