かとじゅんの技術日誌

技術の話をするところ

Scalaの統一アクセス(プロパティ構文)がなかなかイカしてる件

今回は統一アクセス(プロパティ構文)がなかなかイカしてる件について。C#とかRuby,Pythonやってる人からすると何を今頃という感じなのですが。

Scalaで統一アクセス(プロパティ構文)を使う

Scalaでフィールドを宣言する場合は以下のような書き方になります。

class Employee(name_ : String) {
  var name = name_
}

クライアント側のコードは以下。

val e = new Employee("Kato")
e.name = "Kato"
println(e.name) // Kato

追記:
上記のようなコードは以下と同じ意味なので、こちらのほうが短くてわかりやすいので適切。

class Employee(var name : String)

Employeeのnameはpublicなフィールドですね。*1

nameフィールドには大文字の文字列のみを格納したいという制約を付ける場合は、このままではダメです。setterを追加する必要があります。JavaだとsetNameやgetNameを追加することになるので、クライアント側は変更の影響をもろに受けてしまいます。
しかし、Scalaの統一アクセスはそんなことはありません。

class Employee {
  private val re = """([A-Z]+)""".r // 正規表現
  private var name_ :String = _ // _はその型に応じたデフォルト値

  def name = name_
  def name_= (value:String) {
    name_ = value match {
      case re(v) => v // 正規表現でパターンマッチし、該当した値を取得しmatch式の戻り値として返す。
      case _ => throw new IllegalArgumentException
    } // match式の戻り値をname_ に代入
  }
}

このようにすると、以下の代入は事前条件を違反するので例外がスローされます。

val e = new Employee
e.name = "Kato" // IllegalArgumentExceptionがすっ飛びます!
println(e.name) // Kato

setterだけではなくgetterでもフィールドとしては保持していないけど、getterが呼ばれた場合に値を計算する(導出という)場合も同じ要領でgetterを定義できる。

class Employee {
  def name = {
    // ロジックでnameを導出する
  }
}

統一アクセス(プロパティ構文)は何も目新しい機能ではない

古くはEiffel - Wikipediaという言語が発祥だそうです。EiffelWorld Column by Dr. Bertrand Meyerの、ページの上の方に書いてある通りですが、気がつけば多くの近代的な言語でサポートされています。
スクリプト言語では、RubyPython
Ruby

class Employee
  def name
     @name
  end 
  def name=(n) 
     @name = n
  end
end
e = Employee.new
e.name = "Kato"

Python

class Employee:
    def setName(self, name):
      self.__name = name
 
    def getName(self):
      return self.__name

    name = property(getName, setName, doc="name")
e = Employee()
e.name = "Kato"

コンパイラ言語では、Delphi言語(旧ObjectPascal)はこんな感じ。私はDelphi1.0でプロパティ構文を初めて書きました。

type
  TEmployee  = class(TObject)
  private
    FName: string;
    function GetName: string;
    procedure SetName(const Value: string);
  public
    //Nameプロパティ
    property  Name : string      read GetName write SetName;
  end;

implementation

{ TEmployee }

//Nameプロパティ (GET)
function TTestClass.GetName: string;
begin
  Result  :=  FName;
end;

//Keywordプロパティ (SET)
procedure TEmployee.SetName(const Value: string);
begin
  FName  :=  Value;
end;
var
  Employee : TEmployee
begin
  Employee = TEmployee.Create;
  Employee.Name = "Kato";
end;

C#も初期からプロパティ構文をサポートされていました。C#Delphiの生みの親は、アンダース・ヘルスバーグですから、当然と言えば当然。

class Employee {
  private String name;
  public String Name {
    set{ this.name = value; } // setter
    get{ return this.name; } // getter
  }
}
Employee e = new Employee();
e.Name = "Kato";

統一アクセスは、いくつかの言語で普及し現在のバージョンでも利用できるということは、ある程度成功したといえると思います。Scalaの統一アクセス機能もこれらとほぼ同等の機能なので間違いないと思います。setter/getterの手間を最小化できるので便利だと思います。

まとめ

ちょっとしたことだけど、日常的に使うだけにこの機能は欲しい。Javaではいつごろ採用されるのかな。JavaBeansとの整合性とか考えると現実的ではないような気もしますが。Java8でも登場しない感じすかね?よく知らない。。
ともあれ、Scalaなら今すぐ書けますね。クライアント側のコードを変更しなくてもよいから、YAGNI原則に従って、必要になった時点でアクセッサを作ればよいということですね。

補足:
id:ryoasaiさん の質問を受けて、参考までにScalaのフィールド宣言がJavaのコードでどういう表現になるか調査してみました。

class Employee{
  var name:String = _
}

だと、以下で、

public class Employee
    implements ScalaObject {

    public String name() {
        return name;
    }

    public void name_$eq(String s) {
        name = s;
    }

    public Employee() {
    }

    private String name;
}

そして

class Employee{
  private var name:String = _
}

だと、こうなる。

public class Employee
    implements ScalaObject {

    private String name() {
        return name;
    }

    private void name_$eq(String s) {
        name = s;
    }

    public Employee() {
    }

    private String name;
}

んで、今度はこれ。

class Employee{
  private[this] var name:String = _
}

では、このようになりました。id:kxbmapさんのご指摘のとおり、本物のフィールドですね。

public class Employee
    implements ScalaObject {

    public Employee() {
    }

    private String name;
}

オーバーライドする場合は、抽象フィールドの時だけだと思います。こういう場合

trait Employee{
  val name:String
}
class SubEmployee extends Employee {
  override val name:String = "aaa"
}
public interface Employee {

    public abstract String name();
}
public class SubEmployee
    implements Employee, ScalaObject {

    public String name() {
        return name;
    }

    public SubEmployee() {
    }

    private final String name = "aaa";
}

追記:
@ さんより、正規表現の間違いの件 指摘もらいました。修正しました。ありがとうございます。

*1:実際のバイトコードレベルではnameフィールドとname()というgetterが出来ているのですが、それは一旦忘れます。