可視性

badge

Ergの変数には 可視性 という概念が存在します。 今まで見てきた変数は全て プライベート変数(非公開変数) と呼ばれます。これは、外部から不可視の変数です。 例えばfooモジュールで定義したプライベート変数は、別のモジュールから参照できないのです。

# foo.er
x = "this is an invisible variable"
# bar.er
foo = import "foo"
foo.x # AttributeError: Module 'foo' has no attribute 'x' ('x' is private)

対して、 パブリック(公開)変数 というものもあり、こちらは外部から参照できます。 公開変数は.を付けて定義します。

# foo.er
.x = "this is a visible variable"
# bar.er
foo = import "foo"
assert foo.x == "this is a visible variable"

非公開変数には何も付ける必要はないのですが、非公開であることを明示するために::またはself::(型などならSelf::)を付けることもできます。またモジュールならmodule::とすることもできます。

::x = "this is a invisible variable"
assert ::x == x
assert self::x == ::x
assert module::x == ::x

単なる逐次実行の文脈では、プライベート変数はローカル変数とほぼ同義です。内側のスコープからは参照することが出来ます。

::x = "this is a private variable"
y =
    x + 1 # 正確にはmodule::x

::を使うことで、スコープ内の同名変数の区別ができます。 参照したい変数のスコープを左側に指定します。トップレベルの場合はmoduleを指定します。 指定しなかった場合は通常の場合と同じく最も内側の変数が参照されます。

::x = 0
assert x == 0
y =
    ::x = 1
    assert x == 1
    z =
        ::x = 2
        assert ::x == 2
        assert z::x == 2
        assert y::x == 1
        assert module::x == 0

無名サブルーチンのスコープではselfで自身のスコープを指定します。

x = 0
f = x ->
    log module::x, self::x
f 1 # 0 1

::は、プライベートインスタンス属性にアクセスするという役割も持っています。

x = 0
C = Class {x = Int}
C.
    # トップレベルのxが参照される(module::xにするようwarningが出る)
    f1 self = x
    # インスタンス属性のxが参照される
    f2 self = self::x

外部モジュールでの可視性

あるモジュールで定義されたクラスは、実は外部モジュールからでもメソッドを定義できます。

# foo.er
.Foo = Class()
.Bar = Class()
# bar.er
{Foo;} = import "foo"

Foo::
    private self = pass
Foo.
    public self = self::private()
.f() =
    foo = Foo.new()
    foo.public()
    foo::private() # AttributeError

ただし、そのメソッドを使えるのはどちらもそのモジュール内でのみです。 外部で定義された非公開メソッドは、定義モジュール内でのみFooクラスのメソッドから参照できます。 公開メソッドはクラスの外には公開されますが、モジュール外までは公開されません。

# baz.er
{Foo;} = import "foo"

foo = Foo.new()
foo.public() # AttributeError: 'Foo' has no attribute 'public' ('public' is defined in module 'bar')

また、Re-exportする型にメソッドを定義することはできません。 インポート元のモジュールによってメソッドが見つかったり見つからなかったりといった混乱を防ぐためです。

# bar.er
{.Foo;} = import "foo"

.Foo::
    private self = pass # Error
.Foo.
    public self = self::private() # Error

このようなことを行いたい場合はパッチを定義します。

# bar.er
{Foo;} = import "foo"

FooImpl = Patch Foo
FooImpl :=:
    private self = pass
FooImpl.
    public self = self::private()
# baz.er
{Foo;} = import "foo"
{FooImpl;} = import "bar"

foo = Foo.new()
foo.public()

制限公開変数

変数の可視性は完全な公開・非公開しかないわけではありません。 制限付きで公開することもできます。

.の後に[]を付け、その中に「公開する最大の名前空間1の識別子」を指定します。 下の例では、.[.record].recordの名前空間内でのみ、.[module]はモジュール内でのみ公開されます。

# foo.er
.record = {
    .a = {
        .[.record]x = 0
        .[module]y = 0
        .z = 0
    }
    _ = .a.x # OK
    _ = .a.y # OK
    _ = .a.z # OK
}

func x =
    _ = .record.a.x # VisibilityError
    _ = .record.a.y # OK
    _ = .record.a.z # OK
    None

_ = .record.a.x # VisibilityError
_ = .record.a.y # OK
_ = .record.a.z # OK
foo = import "foo"
_ = foo.record.a.x # VisibilityError
_ = foo.record.a.y # VisibilityError
_ = foo.record.a.z # OK

名前空間はコンマ区切りで複数指定することも出来ます。

.[.record, func]x = 0

ところで、クラスのプライベート属性はサブクラスからアクセス出来ません。

C = Class {i = Int}

D = Inherit C
D.
    f self = self::i # VisibilityError

あるサブクラスからアクセスできるようにしたい場合は、以下のように指定します。

C = Class {.[D]i = Int}

D = Inherit C
D.
    f self = self.i

サブクラス全体に公開する場合は、.[<: Self]とします。 これは他言語ではprotectedに相当するものです。

C = Class {.[<: C]i = Int}

1 Ergにおいて名前空間は、名前とオブジェクトの対応の集合を指す。インスタントスコープを作る変数の識別子やモジュール・関数・クラス・レコードが名前空間と同一視される。関数・クラス・レコードは識別子に束縛せずに生成することができるため、これらは本来無名名前空間を作る。しかし識別子に束縛されると、識別子と同名の名前で上書きされる。