忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

IronScheme doで配列を回すと…

doで配列を回す

勉強中のIronSchemeですが、さっそくわかりません!
doという、C#で言うところのforに相当するものがあるのですが、これと配列を組み合わせると不可解な現象が起きます。
(ちなみに、doは真のSchemerは本当は使わないそうです。)

(import (rnrs) (ironscheme clr))

(clr-using System)
(define myArray (clr-new-array Int32 4))


(do ((index 0 (+ index 1)))
	((= index (clr-prop-get Array Length myArray)))
	(clr-indexer-set! Int32[] myArray index index))


(do ((index 0 (+ index 1)))
	((= index (clr-prop-get Int32[] Length myArray)))
	(clr-static-call
		Console
		WriteLine
		(clr-indexer-get Int32[] myArray index)))

(clr-static-call Console ReadLine)

これはC#でいうと以下の様なプログラムに相当します。
(厳密にはC#版は上手く動くので違うのですが)

using System;

class Program
{
    public static int[] myArray = new int[4];

    static void Main()
    {
        for (int index = 0; !(index == myArray.Length); index++)
        {
            myArray[index] = index;
        }

        for (int index = 0; !(index == myArray.Length); index++)
        {
            Console.WriteLine(myArray[index]);
        }

        Console.ReadLine();
    }
}



私がやりたかったのはこういうことです:
長さ4の配列を作り、それを{ 0, 1, 2, 3 } というふうに初期化し、それを出力するのです。

Expected:
0
1
2
3

ですから上のプログラム(IronScheme)を実行して下のような結果を受け取った時には驚きました。

Actual:

36561384
36561408
36561432
36561456

……
!!?

このやけに大きな数字は一体!?
ためしてみると実行するたびにこの数字は変わっていきました。

これではまるで何かのアドレスのようです。
いや.netですからハッシュコードでしょうか。
でもInt32のGetHashCode()はそれ自身を返すはず…
一体どういうことでしょう。

色々やってみる

一つの考えとしてclr-indexer-getかclr-indexer-set!がおかしいのではないかというのが思い浮かびます。
配列への値のセットやゲットが上手くいっていないのです。
そこで次のような単純なIronSchemeを書きます。
(import (rnrs) (ironscheme clr))

(clr-using System)
(define myArray (clr-new-array Int32 1))
(clr-indexer-set! Int32[] myArray 0 42)
(display (clr-indexer-get Int32[] myArray 0))

(clr-static-call Console ReadLine)
C#でいうとこんな感じです:

using System;

class Program
{
    public static int[] myArray = new int[1];

    static void Main()
    {
        myArray[0] = 42;
        Console.Write(myArray[0]);
        Console.ReadLine();
    }
}

これは長さ1の配列を作り、最初に42を代入し、それを表示するプログラムです。
もし配列のゲットやセットがうまくいっていないのなら、ここで42ではない、変な数字が表示されるはずです。
どうなるでしょうか。
(以下IronSchemeの結果を書きます)

42

あれれ…42と出力されました。
配列には42をセットしましたから、これは正常です。
配列の値のセットとゲットは上手く行っているようです。

すると変数が悪いのでしょうか?
変数を使うことによってなにか動的言語に詳しくないプログラマが陥る罠が現れるのでしょうか?
確かめてみましょう。
まずは手軽なグローバル変数を。

(import (rnrs) (ironscheme clr))

(clr-using System)
(define myArray (clr-new-array Int32 1))
(define value 42)
(clr-indexer-set! Int32[] myArray 0 value)
(display (clr-indexer-get Int32[] myArray 0))

(clr-static-call Console ReadLine)

using System;

class Program
{
    public static int[] myArray = new int[1];
    public static int value = 42;

    static void Main()
    {
        myArray[0] = value;
        Console.Write(myArray[0]);
        Console.ReadLine();
    }
}



これは先ほどとほとんど同じプログラムです。
唯一違うのは42という数字を間接的に配列に代入している点です。
間にグローバル変数を挟んでいるのです。
やはり先程と同じく、これが正常に動けば42と表示され、
問題があれば変な42以外の数字が表示されます。
42

42です。
うーん当たり前といえば当たり前です。
ここには問題ありません。
しかし念のためローカル変数も調べてみましょう。

(import (rnrs) (ironscheme clr))

(clr-using System)
(define myArray (clr-new-array Int32 1))
(let 
	((value 42))
	(clr-indexer-set! Int32[] myArray 0 value)
	(clr-static-call Console WriteLine value))
(clr-static-call Console WriteLine (clr-indexer-get Int32[] myArray 0))

(clr-static-call Console ReadLine)

using System;

class Program
{
    public static int[] myArray = new int[1];

    static void Main()
    {
        int value = 42;
        myArray[0] = value;
        Console.Write(myArray[0]);
        Console.ReadLine();
    }
}

結果はどうなるでしょう。(IronScheme版)

42
39708184

……おお!!
あり期待していませんでしたが、問題はここにありました!

どういうわけか数字をConsole.WriteLineは正しく扱っているのに、インデクサはおかしく扱っています。
そういえば配列のインデクサは本当のインデクサではなかった気もします。
関係あるのでしょうか?

なにはともあれ原因はここにあるようです。
さてどうするべきでしょうか。
原因がわかっても解決方法は?

とりあえず、型が関係しているような気がします。
たとえばIronSchemeの数字はInt32ではないとか。
たしかSchemeでは全てが参照型だった気がします。
変数の型を調べてみましょう。

(import (rnrs) (ironscheme clr))

(let ((value 42))
	(clr-static-call 
		System.Console 
		WriteLine 
		"{0} {1}" 
		(clr-call System.Object GetType value)
		value))

(clr-static-call System.Console ReadLine)

class Program
{
    public static int[] myArray = new int[1];

    static void Main()
    {
        int value = 42;
        System.Console.WriteLine("{0} {1}", value.GetType(), value);
        System.Console.ReadLine();
    }
}

このプログラムは変数indexの型と値を表示します。
もし変数の型に問題があるのなら型はSystem.Int32以外が表示されるでしょう。
結果は・・・
System.Int32 42


残念、ここには問題はありませんでした。
indexはちゃんとSystem.Int32の42です。
IronSchemeは謎の数字の型を使っていて、それゆえに変な値が表示されるのでは?
という私の仮説は脆くも崩れ去りました。


解決方法

でも念のため元のコードでキャストしてみましょう。

(import (rnrs) (ironscheme clr))

(clr-using System)
(define myArray (clr-new-array Int32 4))


(do ((index 0 (+ index 1)))
	((= index (clr-prop-get Array Length myArray)))
	(clr-indexer-set! Int32[] myArray index (clr-cast Int32 index)))


(do ((index 0 (+ index 1)))
	((= index (clr-prop-get Int32[] Length myArray)))
	(display (clr-indexer-get Int32[] myArray index))
	(newline))

(clr-static-call Console ReadLine)
C#版:
using System;

class Program
{
    public static int[] myArray = new int[4];

    static void Main()
    {
        for (int index = 0; !(index == myArray.Length); index++)
        {
            myArray[index] = (int)index;
        }

        for (int index = 0; !(index == myArray.Length); index++)
        {
            Console.WriteLine(myArray[index]);
        }

        Console.ReadLine();
    }
}

0
1
2
3
あれ…
上手くいってしまいました。
うーん拍子抜けです。
これは一体何だったのでしょう…

indexをキャストするとうまくいくことから、IronSchemeではindexが何か別のものであった可能性が考えられます。
しかしその可能性は上のサンプルで否定されました。
ますますもってわかりません。

しかし上手く配列を回せたのでOKということにしましょう!

ちなみに

配列を使わない場合はどうなるかというと、こうなります。

(import (rnrs) (ironscheme clr))

(clr-using System)
(define myValue 0)

(let 
	((value 42))
	(set! myValue value)
	(clr-static-call Console WriteLine value))
(clr-static-call Console WriteLine myValue)

(clr-static-call Console ReadLine)

42
42
正常です。

拍手[0回]

PR