変数と属性のスコープ規則

Ring のスコープ規則と変数の検出方法、および名前衝突の解説と解決方法と回避方法です。

この情報は Ring で大規模アプリケーションの開発をはじめるときに重要です。

アプリケーションには下記が使用されています。

  • グローバル変数 (不使用を心がけてください)

  • クラス (オブジェクト指向)

  • 括弧 { } によるオブジェクトのアクセス

  • 宣言型プログラミング

  • 自然言語プログラミング

三種類のスコープ

Ring には三種類のスコープがあります。

  1. パブリックスコープとグローバルスコープ - 各変数はステートメント部で定義されます (関数とクラスの前)

  2. オブジェクトスコープ - オブジェクトの内側にあるとき (クラスのメソッドの内側または { } の使用によるオブジェクトへのアクセス)

  3. ローカルスコープ - 関数とメソッドに関連付けられています

変数の定義と変数へのアクセス

  1. Ring はレキシカルスコープを使用しています。つまり、変数のスコープは変数を定義した場所により決定します。

  2. { } 括弧内でのオブジェクトのアクセス時、現在有効なオブジェクトのスコープを対象となるオブジェクトのスコープへ変更します。今まで通り、グローバルスコープとローカルスコープへアクセスできます。

  3. 'Class' キーワードとクラス名末尾に、変数名の定義を記述したとしても、今まで通りグローバルスコープへアクセスできます。

この範囲 (クラスの範囲 - クラス名の後、およびメソッドの前) にあるものは、

  • グローバルスコープ ----> グローバルスコープ

  • オブジェクトスコープ ----> オブジェクトスコープ

  • ローカルスコープ ----> オブジェクトスコープ

注釈

クラスの範囲にあるローカルスコープからでも、この範囲のオブジェクトスコープを指すため、多重括弧は使用可能でありローカルスコープからクラスのオブジェクトスコープへのアクセスができます。

ちなみに

この範囲での定義によりウィンドウとコントロールの属性を作成できます。

ちなみに

クラスの範囲にてオブジェクトの作成、および括弧 { } でオブジェクトへアクセスした後に、括弧の内側で Self.属性 を使うとクラスを使えます (アクセスしようとしているオブジェクトではありません)。この理由はローカルスコープからクラスを呼び出すことができるからです。

  1. 関数の仮引数は、自動的にローカルスコープへ定義されます。

Ring による変数の検出方法

1 - 最初にローカルスコープを検索

  • 見つからない!

2 - オブジェクトスコープの検索

  • 見つからない!

3 - パブリックスコープの検索

  • 見つからない ----> ランタイムエラー

  • 見つかった ----> 次回の検索を回避するために、最適化できるか確認します (性能改善のためのポインタとキャッシュ)。

オブジェクト.属性の用法

オブジェクト.属性 を使うときはオブジェクト属性に限り検索を行います。

つまり、ローカルスコープまたはグローバルスコープのオブジェクト属性では検索は行いません。

注釈

Self.属性を使うときは属性を検索する前に Self の検索を最初に行います。

Self オブジェクト

Selft オブジェクトは現在のオブジェクトを参照するためにクラスのメソッドから使えます!

クラスのメソッドの内側であり Self を使うときは、このクラスから作成されるオブジェクトを意味します。

クラスのメソッドの内側で括弧 { } を使うと、現在のオブジェクトのスコープを変更します。 また、 Self でオブジェクトへアクセスするときに、括弧の内側にある参照を変更します。

クラスの範囲 (クラス名の後、およびメソッドの前) の内側はオブジェクトスコープ、 およびローカルスコープからオブジェクトへのアクセスすることもできます。 この範囲で Self を使うと、常にクラスのオブジェクトへ参照されます。 オブジェクトのスコープの変更で括弧を使用後に、括弧の内側で Self を使用した場合でも、 クラスの範囲内にあるため、 Self はクラスオブジェクトへの参照になります (既に括弧でアクセスしているオブジェクトではありません)。

  • グローバルスコープ ---> グローバルスコープ

  • オブジェクトスコープ ---> オブジェクトスコープ

  • ローカルスコープ ---> オブジェクトスコープ

括弧を使うとオブジェクトのスコープのみ変更されます。 Ring は変数を検索するときに、ローカルスコープを最初に検索するためクラスの内側にある Self を検出します。

Ring における変数と属性の定義方法

Ring は代入操作で変数名を使用します。

1 - 変数名で検索

2 - 見つからない ---> ランタイムエラーの回避と現在のスコープへ変数を定義します。

3 - 見つかった ---> 変数を使用しますが現在のスコープへ変数を一切定義しません。

  • グローバルの範囲 (関数またはクラスの前) において現在のスコープはグローバルスコープになります。

  • クラスの範囲 (クラス名の後、およびメソッドの前) において現在のスコープはオブジェクトの属性になります。

  • 関数とメソッドにおける現在のスコープはローカルスコープになります。

クラス属性とグローバル変数の間での名前衝突

この用例を参照してください:

name = "test"
o1 = new person
see o1

class person
        name
        address
        phone

前述の用例ではグローバル変数 ‘name’ が person クラスの内側にあります。

Ring は‘name’ 変数を使用するにとき検索処理の開始、 および検出します。

見つかった ---> 見つかったものを使用

見つからない ---> 新しい属性の定義

しかし、変数名はグローバル変数であるため、検出後に使用されます!

属性名が存在しない! オブジェクトへ追加します。

解決方法① - Main 関数の使用

func main
        name = "test"
        o1 = new person
        see o1

class person
        name
        address
        phone

解決方法② - $ などのグローバル変数名に特殊記号を使用

$name = "test"
o1 = new person
see o1

class person
        name
        address
        phone

解決方法③ - AddAttribute() メソッドの使用

name = "test"
o1 = new person
see o1

class person
        AddAttribute(self,"name")
        address
        phone

解決方法④ - 属性名の先頭に Self を使用

name = "test"
o1 = new person
see o1

class person
        self.name
        address
        phone

この名前衝突に関する最善の解決方法は?

1 - グローバル変数で $ 記号を使用

2 - オプション扱い : グローバル変数の使用を避ける、または Main 関数の使用

実際は両方とも実施します。

そのほかの解決方法

  • 属性名の先頭に Self を使います。または AddAttribute() を使用します。

クラス属性とローカル変数の間での名前衝突

この名前衝突は、括弧でオブジェクトにアクセスするときに発生します。

用例:

func main
        name = "nice"
        o1 = new person {name="mahmoud" address="Egypt"  phone = 000 }
        see o1

class person
        name
        address
        phone

前述の用例にはローカル変数名があります。

この変数の値は、オブジェクトの属性ではなく“mahmoud”が設定されます。

解決方法① : Self の使用

func main
        name = "nice"
        o1 = new person {self.name="mahmoud" address="Egypt"  phone = 000 }
        see o1

class person
        name
        address
        phone

解決方法② : ローカル変数名の変更

func main
        cName = "nice"
        o1 = new person {name="mahmoud" address="Egypt"  phone = 000 }
        see o1

class person
        name
        address
        phone

解決方法③ : 括弧の変更およびドット演算子を使用

func main
        name = "nice"
        o1 = new person
        o1.name ="mahmoud"
        o1.address ="Egypt"
        o1.phone = 000
        see o1

class person
        name
        address
        phone

括弧によるクラスメソッド内オブジェクトへのアクセス方法

クラスのメソッドの内側では三種類のスコープ (ローカルスコープ、オブジェクトスコープとグローバルスコープ) があることを思い出してください。 これはオブジェクト属性とメソッドへアクセス可能であると期待しており、 括弧でオブジェクトの属性とメソッドへアクセスするまでは本当です。 この場合は、オブジェクトのスコープが別のオブジェクトへ切り替えられるためです。

new point { test() }

class point
        x=10 y=20
        func test
                see x + nl + y + nl # 正常に動作します。
                myobj = new otherclass {
                        see name + nl
                        see x + nl + y + nl # エラー!
                }

class otherclass
        name = "test"

実行結果:

10
20
test

Line 8 Error (R24) : Using uninitialized variable : x
In method test() in file methodbraceerror.ring
called from line 5  in file methodbraceerror.ring

さて、前述の問題の解決方法は?

解決方法 (1) : 括弧の外側にあるクラスの属性へアクセスするためのコードを記述する。

new point { test() }

class point
        x=10 y=20
        func test
                see x + nl + y + nl # 正常に動作します。
                myobj = new otherclass {
                        see name + nl
                }
                see x + nl + y + nl # 括弧の外側 - 正常に動作します。


class otherclass
        name = "test"

実行結果:

10
20
test
10
20

解決方法② : 括弧を使用しない

new point { test() }

class point
        x=10 y=20
        func test
                see x + nl + y + nl
                myobj = new otherclass
                see myobj.name
                see x + nl + y + nl

class otherclass
        name = "test"

解決方法③ : Self オブジェクトのコピー

この解決方法は、括弧でクラス属性へアクセスしたいときに使用します (読み取り)。

new point { test() }

class point
        x=10 y=20
        func test
                oSelf = self
                see x + nl + y + nl
                myobj = new otherclass {
                        see name + nl
                        see oself.x + nl + oself.y + nl
                }

class otherclass
        name = "test"

実行結果:

10
20
test
10
20

この行を参照してください

oself = self

前行の問題はオブジェクトの新しいコピーあることです。 この理由は Ring の代入演算子はリストと値によるオブジェクト (参照ではない) はコピーしないからです。

新しいオブジェクトへアクセスする場合は問題はありません (読み取り)。

オブジェクトの属性を変更した場合は問題があります (そのためコピーを変更します!)

注釈

括弧はコピーを行うときに再度使えます。

new point { test() }

class point
        x=10 y=20
        func test
                oSelf = self
                see x + nl + y + nl
                myobj = new otherclass {
                        see name + nl
                        oSelf {
                                see x + nl + y + nl
                        }
                }

class otherclass
        name = "test"

GUI アプリケーションでは、様々なメソッドからコントロールへアクセス可能な属性として、 Window オブジェクトがあるクラスを作成します。 括弧でメソッドの内側にあるオブジェクトへアクセスするときは、前述の情報を思い出してください。 この場合は、オブジェクトとの属性へアクセスできないため Self オブジェクトをコピーした場合は、コピー先での作業となります。 新しいコントロールの作成は、コピーと関連付けられていますがアクセスはできません。

クラスのメソッド内にある括弧からクラスの属性にアクセスするには

クラスのメソッドからクラスの属性へアクセスするために、属性やメソッド名の先頭に Self による参照を選択する方法があります。 クラスのメソッド内で括弧 { } を使うと、有効なオブジェクトのスコープを変更することになり、 クラスの属性への意図しない (directly) アクセスを防げます。また Self の使用は、括弧でアクセスするオブジェクトを変更できるため Self による参照は効果がありません。

この場合、属性を読み取りたい場合は、括弧の使用前に Self オブジェクトを必ずコピーしてください。 また、括弧の後に、オブジェクトの属性からローカル変数へコピーしたい場合は、必ず属性を修正してください。

この場合は、括弧の内側にある属性の読み取り、または変更をしたいときに発生します。

Class MyApp

        oCon   # 属性

        # こちらへコードを記述してください。

        Func OpenDatabase
                # こちらへコードを記述してください。
                new QSqlDatabase() {
                        oCon = addDatabase("QSQLITE") {
                                setDatabaseName("weighthistory.db")
                                open()
                        }
                }
                self.oCon = oCon
                # こちらへコードを記述してください。

前述の用例で connection オブジェクトを作成して oCon 属性の内側で保存したいとします。

このオブジェクトは QSQLDatabase() オブジェクトのアクセス後に使用する addDatabase() メソッドからの出力です。

括弧の内側では MyApp クラスから作成したオブジェクトによる Self 参照の使用はできません。 ここで Self 参照を使うと、括弧でオブジェクトをアクセスすることになるからです。

ローカル変数 oCon を作成してから、括弧の後に oCon 属性を変数へコピーすることにより、 前述の問題を解決しました。

このコードは別の解決方法です。

Class MyApp

        oCon   # 属性

        # こちらへコードを記述してください。

        Func OpenDatabase
                # こちらへコードを記述してください。
                oCon = new QSqlDatabase()
                oCon = oCon.addDatabase("QSQLITE") {
                        setDatabaseName("weighthistory.db")
                        Open()
                }
        # こちらへコードを記述してください。

このコードは優れた解決方法です。

Class MyApp

        oCon   # 属性

        # こちらへコードを記述してください。

        Func OpenDatabase
                # こちらへコードを記述してください。
                new QSqlDatabase() {
                        this.oCon = addDatabase("QSQLITE") {
                                setDatabaseName("weighthistory.db")
                                Open()
                        }
                }
                # こちらへコードを記述してください。

注釈

括弧の内側でクラスの属性 (oCon) へアクセスするために This.属性 を使用しています。

GUI アプリケーションのウィンドウごとにクラスを作成するには

ウィンドウ用のクラスを作成するための優れた方法は、クラス名の直後にウィンドウを定義することです。

この領域で多重括弧を使うとウィンドウとコントロールの定義に関する問題を起こさずに、メソッドから属性にアクセスできます。

用例:

Load "guilib.ring"

new qApp
{
        $ObjectName = "oFirstWindow"
        oFirstWindow = new FirstWindow

        $ObjectName = "oSecondWindow"
        oSecondWindow = new SecondWindow

        exec()
}

Class FirstWindow

        win = new qWidget() {
                setgeometry(0,50,300,200)
                setWindowTitle("First Window")
                label1 = new qLabel(win)
                {
                        setgeometry(10,10,300,30)
                        setText("0")
                }
                btn1 = new qPushButton(win)
                {
                        move(100,100)
                        setText("Increment")
                        setClickEvent($ObjectName+".increment()")
                }
                show()
        }

        Func Increment
                label1 {
                        setText( "" + ( 0 + text() + 1 ) )
                }


Class SecondWindow

        win = new qWidget() {
                setgeometry(400,50,300,200)
                setWindowTitle("Second Window")
                label1 = new qLabel(win)
                {
                        setgeometry(10,10,300,30)
                        setText("0")
                }
                btn1 = new qPushButton(win)
                {
                        move(100,100)
                        setText("Decrement")
                        setClickEvent($ObjectName+".decrement()")
                }
                show()
        }

        Func Decrement
                label1 {
                        setText( "" + ( 0 + text() - 1 ) )
                }

クラス範囲にある括弧内の Self と Self 間との名前衝突

クラスの領域 (クラス名の後、そしてメソッドの前) で属性を定義します。

この領域では、グローバルスコープへアクセスできます。そして、ローカルスコープはオブジェクトスコープを指します。

三種類のスコープ

  • グローバルスコープ ---> グローバルスコープ

  • オブジェクトスコープ ---> オブジェクトスコープ

  • ローカルスコープ ---> オブジェクトスコープ

三種類のスコープ

New Account {
        see aFriends
}

Class Account
        name = "Mahmoud"
        aFriends = []
        aFriends + new Friend {
                name = "Gal"
        }
        aFriends + new Friend {
                name = "Bert"
        }

Class Friend
        name

実行結果:

name: NULL
name: NULL

前述の用例における問題は account クラスには “name” 属性があり、 Friend クラスにも同名属性 “name” があります。

括弧内で self.name を使うと前述の用例と同じ結果になります!

New Account {
        see aFriends
}

Class Account
        name = "Mahmoud"
        aFriends = []
        aFriends + new Friend {
                self.name = "Gal"
        }
        aFriends + new Friend {
                self.name = "Bert"
        }

Class Friend
        name

この名前衝突に関して、括弧の内側で self.name でも解決しない理由は?

このようなクラスの範囲になるからです。

  • グローバルスコープ ---> グローバルスコープ

  • オブジェクトスコープ ---> オブジェクトスコープ (Account クラス)

  • ローカルスコープ ---> ローカルスコープ (Account クラス)

括弧を使うとき、オブジェクトのスコープは変更されるため、このようになります。

  • グローバルスコープ ---> グローバルスコープ

  • オブジェクトスコープ ---> オブジェクトスコープ (Friend クラス)

  • ローカルスコープ ---> ローカルスコープ (Account クラス)

Ring では、ローカルスコープを最初に検索するため、 self.name を使うときは Account クラスを使用します。

様々な解決方法があります。

解決方法① : リストによるオブジェクトへのアクセス

New Account {
        see aFriends
}

Class Account
        name = "Mahmoud"
        aFriends = []
        aFriends + new Friend
        aFriends[len(aFriends)] {
                aFriends[len(aFriends)].name = "Gal"
        }
        aFriends + new Friend
        aFriends[len(aFriends)] {
                aFriends[len(aFriends)].name = "Bert"
        }

Class Friend
        name

解決方法② : name 属性を設定するために friend クラスでメソッドを作成

New Account {
        see aFriends
}

Class Account
        name = "Mahmoud"
        aFriends = []
        aFriends + new Friend {
                setname("Gal")
        }
        aFriends + new Friend {
                setname("Bert")
        }

Class Friend
        name
        func setname cName
                name = cName

解決方法③ : 属性を設定するために account クラスでメソッドを作成

New Account {
        see aFriends
}

Class Account
        name = "Mahmoud"
        aFriends = []
        friend("Gal")
        friend("Bert")

        func friend cName
                aFriends + new Friend {
                        name = cName
                }

Class Friend
        name

解決方法④ : 宣言型プログラミング

New Account {
        name = "mahmoud"
        friend {
                name = "Gal"
        }
        friend {
                name = "Bert"
        }
        see aFriends
}

Class Account
        name
        aFriends = []
        friend
        func getfriend
                aFriends + new Friend
                return aFriends[len(aFriends)]

Class Friend
        name

実行結果:

name: Gal
name: Bert

括弧による現在のオブジェクトスコープの除外方法

括弧から現在のオブジェクトスコープを別のオブジェクトスコープへ変更します。 クラス属性の変更、および同名変数の使わずに処理できます。

new point {x=10 y=20 z=30 start() }
class point x y z
        func start
                see self # x y z の値を表示 (10,20,30)
                new Local {
                        x = 100
                        y = 200
                        z = 300
                }
                see self # x y z の値を表示 (10,20,30)
                see x + nl # 100 の表示
                see y + nl # 200 の表示
                see z + nl # 300 の表示
                Self {  # 利点なし - ローカルスコープの最初の検索で完了します。
                        see x + nl # 100 の表示
                        see y + nl # 200 の表示
                        see z + nl # 300 の表示
                }
                see self.x + nl # 10 の表示
                see self.y + nl # 20 の表示
                see self.z + nl # 30 の表示

class Local

実行結果:

x: 10.000000
y: 20.000000
z: 30.000000
x: 10.000000
y: 20.000000
z: 30.000000
100
200
300
100
200
300
10
20
30

For ループでローカルスコープを使用

Ring 1.8 より、 For ループでの新規識別子 (変数) を定義するときは、ローカルスコープで定義します。

用例:

x = 10
? x             # 10 の表示
test1()
? x             # 10 の表示
test2()
? x             # 10 の表示

func test1
        for x = 1 to 5
        next
        ? x     # 6 の表示

func test2
        list = 1:5
        for x in list
        next
        ? x     # NULL の表示 ("For In" ループでは、ループ完了後に参照を破棄します)

実行結果:

10
6
10
NULL
10

スコープ規則のまとめ

最初に次のことを覚えておいてください。

1 - 各種プログラミング言語には言語の目的に基づいたスコープ規則があります。

2 - 小規模プログラミングと大規模プログラミングは異なります。

3 - とあるプログラミング言語は小規模プログラミング用に設計されていますが、それ以外は大規模プログラミング用に設計されています。

4 - プログラミングで、スコープへのアクセスが複数ある場合があります。 - 正確に管理されていない場合は問題になります。

5 - 可視スコープの本数を削減すると、安全性の維持と向上につながります。

6 - とあるプログラミング言語では、不自由で独善的なスコープの管理方法を強制しています!

Ring では

1 - まず柔軟性、そして次に安全性を求めて設計された特別かつ「非常に単純明快」なスコープ規則があります

2 - Ring は小規模プログラミングと大規模プログラミングに対応するように設計されています。

3 - Ring にはプロジェクトの規模に基づいて選択可能な各種プログラミング・パラダイムがあります。ターゲットとなるプロジェクトで悪いパラダイムを選択した、または間違っているか一般的ではない方法によりパラダイムを使用するとエラーになります。

4 - Ring には選択肢があります。グローバル変数の使用・不使用を選べます。特殊記号 $ の指定、または除去できます。オブジェクト指向を使用する、または手続き型の使用を継続できます。クラス範囲 (クラスの範囲 - クラス名の後、およびメソッドの前) で属性の使用、またはコードで属性を使えます。

5 - このスコープ規則をご確認になり、記載されていることについて考えた後に好きな方法を使用してください。

スコープ規則:

1 - プログラムのコード全体で扱えるスコープは最大三種類です (ローカルスコープ、オブジェクトのスコープとグローバルスコープ)。

2 - 変数の検索順は、最初にローカルスコープ、次にオブジェクトのスコープ、最後にグローバルスコープです。

3 - 手続き、またはメソッドは、括弧 { } でオブジェクトへのアクセス、および現在のオブジェクトのスコープをいつでも変更できます。

4 - クラスの範囲 (クラス名の後、およびメソッドの前) において、これはオブジェクトスコープでオブジェクトのスコープとローカルスコープの両方を指す特別な範囲です。すなわち、この範囲で定義する各変数は属性になるため、ローカル変数ではありません。

5 - 変数 (スコープとクラスの範囲) の定義前に検索処理で変数が検出された場合は検出された変数を使用します。

6 - 関数とメソッドの仮引数は関数、またはメソッドへのローカル変数として自動的に定義されます。

7 - オブジェクト.属性 を使うとオブジェクト属性のみ検索します。

8 - Self.属性 を使うと先頭にある Self を最初に検索してから Self の属性を検索します。

9 - クラスの範囲 (クラス名の後、そしてメソッドの前) の内側にある Self 参照はクラスから作成されたオブジェクトのスコープを指します。

10 -メソッドの内側にある Self 参照は括弧による参照でオブジェクトへアクセスするときに変更されます。

11 - クラスの範囲 (クラス名の後、そしてメソッドの前) に変数名を記述すると使用対象、または定義として扱われます。

12 - クラスの範囲で Self.属性 を使うと検索対象となるオブジェクトのスコープの個数を削減します (グローバルスコープとの名前衝突を回避します)。

これらの規則から名前衝突が何故発生するのか、そして回避するにはどうしたら良いか理解できます。

名前衝突を避けるための簡単な助言として、スコープ規則を使うのが最良の方法です。

1 - グローバル変数の回避をしてください。

2 - Main 関数の使用 - これはグローバル変数の回避に有効です。

3 - グローバル変数を多用する場合は変数名の先頭に $ を使用してください。

4 - クラスの範囲で三番目 ($ の使用) の助言を考慮しない場合は、属性を定義するときに self.属性 を使用します。

5 - オブジェクトのスコープを変更したくない場合は、オブジェクト { 属性 } と オブジェクト { メソッド() } の代わりに オブジェクト.属性 と オブジェクト.メソッド() を使用します。

6 - 多重括弧をクラスで使用する場合 - この範囲で { } + によりクラス属性へアクセスするために、オブジェクトのアクセス権がある場合はクラス範囲の使用に関して可能ならば考慮してください。

7 - クラスのメソッドの内側、および多重括弧が使用されている場合は、括弧ごとにオブジェクトのスコープは変更されるため、直ちにクラスの属性へのアクセス権を失いますが、括弧 { } でローカルスコープの前後へアクセスできます。括弧からクラスの属性を読み取り、 または修正する場合は This.属性 を使用してください。この理由として ‘This’ を使用することは「このクラスからオブジェクトが作成される」ことを意味するのとは違い ‘Self’ (現在のスコープにあるオブジェクト) を意味します。

前述の要点を全て理解したならば、本章を会得したことになります。