読者です 読者をやめる 読者になる 読者になる

Suzuのトレイトは「戻り値としてモジュールを返す関数」

Suzu

オリジナルのトレイトは,合成可能なメソッドの集合をクラスに追加することでコードの再利用を行う仕組みであり,多重継承の代替機構として活用できます.

Suzuには継承がなく,トレイトによってコードの再利用を行います.またSuzuのメソッドはクラスではなく環境に格納されるため,一般的なトレイトとはかなり趣が異なります.

Suzuのトレイトは戻り値としてモジュールを返す関数として表現されます.トレイトは既存のクラスとメソッドを受け取り,新たなメソッドを定義したモジュールを返します.戻り値をopenすることでそのスコープにおいて新たなメソッドの定義が有効になります.

trait Account(C, C#balance, C#(balance=)):
  def C#deposit!(self, x):
    self.balance = self.balance + x
  end

  def C#withdraw!(self, x):
    self.balance = self.balance - x
    if(self.balance < 0):
      self.balance = 0
    end
  end

  export C#deposit!, C#withdraw!
end

class BankAccount = make_bank_account:
  mutable balance
end

open Account(BankAccount, BankAccount#balance, BankAccount#(balance=))

def create_bank_account():
  make_bank_account(0)
end

let account = create_bank_account()

account.balance = 200
assert(account.balance == 200)
account.deposit!(50)
assert(account.balance == 250)
account.withdraw!(100)
assert(account.balance == 150)
account.withdraw!(200)
assert(account.balance == 0)

class StockAccount = make_stock_account:
  mutable num_shares
  price_per_share
end

def StockAccount#balance(self):
  self.num_shares * self.price_per_share
end

def StockAccount#(balance=)(self, x):
  self.num_shares = x / self.price_per_share
end

open Account(StockAccount, StockAccount#balance, StockAccount#(balance=))

def create_stock_account():
  make_stock_account(10, 30)
end

let stock = create_stock_account()

assert(stock.num_shares == 10)
assert(stock.price_per_share == 30)
assert(stock.balance == 300)
stock.balance = 150
assert(stock.num_shares == 5)

stock.balance = 600
assert(stock.balance == 600)
assert(stock.num_shares == 20)
stock.deposit!(60)
assert(stock.balance == 660)
assert(stock.num_shares == 22)

上の例では,BankAccountStockAccountに共通する操作をAccountトレイトとして定義しています.

トレイトはモジュールを返す関数なので,OCamlのファンクタのような使い方もできます.

begin:
  open Reflect(Option::bind)
  let result1 = reset:
    let a = reflect(List::find([1, 0, 3], ^(n){ n == 2 }))
    let b = reflect(List::find([2, 3, 1], ^(n){ n == 3 }))
    Some([a, b])
  end
  assert(result1 == None())
  let result2 = reset:
    let a = reflect(List::find([1, 0, 3, 2], ^(n){ n == 2 }))
    let b = reflect(List::find([2, 3, 1], ^(n){ n == 3 }))
    Some([a, b])
  end
  assert(result2 == Some([2, 3]))
end

begin:
  open Reflect^(m, proc):
    List::flatten(List::map(m, proc))
  end
  let result = reset:
    let a = reflect([1, 2, 3])
    let b = reflect([4, 5, 6])
    let c = a * b
    if(c % 6 != 0):
      []
    else:
      [c]
    end
  end
  assert(result == [6, 12, 12, 18])
end

Reflectの定義は以下のようになっています.

trait Reflect(bind):
  def reflect(m):
    shift^(k):
      bind(m, k)
    end
  end
  export reflect
end

この例ではReflectトレイトにbind関数を渡して戻り値をopenすることで,モナディックな操作をノンモナディックに記述できる関数reflectをローカルに定義しています(限定継続オペレータであるshiftresetを使用しています).

Suzuのモジュールは他にも,includeキーワードによって合成したり,exceptキーワードによってメソッドや変数を取り除いたりできます. Suzuは環境にメソッドを直接格納することにより,メソッドの集合であるトレイトの操作を他の言語でも馴染み深いモジュールの操作の中に組み込むことが可能となっています.

柔軟なメソッド定義が可能なプログラミング言語Suzu

Suzu

自作のプログラミング言語SuzuをGitHubにて公開しています.

Suzuは以下のような機能を持ちます.

  • 柔軟なメソッド定義
    • ローカル変数ならぬローカルメソッドの定義
    • シャドーイング
    • モジュールからのインポート・エクスポート
    • 関数の仮引数としての指定
  • モジュールを返す関数としてのトレイト
  • ユーザー定義演算子
  • 限定継続
  • 複数の関数リテラルラベル付き引数として渡せる
  • バリアント,レコード,パターンマッチング

最大の特徴は,柔軟なメソッド定義が可能であることです.

RubyにはRefinementsという機能があり, スコープを限定してメソッドを再定義することができます. クラスにメソッドを格納するRubyのような言語ではこのような仕組みを用意することは妥当でしょう.

これに対しSuzuは,環境にメソッドを直接格納することで,複雑な仕組みを用意することなく メソッドの局所的な再定義を可能にします.

assert(3 / 2 == 1)
begin:
  def Int::C#(/)(self, other):
    Float::from_int(self) / Float::from_int(other)
  end
  assert(3 / 2 == 1.5)
end
assert(3 / 2 == 1)

メソッドクラス名とメソッド名の組をキーとして環境に直接格納されます. 上の例ではクラス名Int::Cメソッド(/)の組Int::C#(/)に対しメソッドを定義しています. これによりbeginからendまでのスコープでのみ除算演算子が再定義されます.

定義したいメソッド群をモジュールとして提供することもできます.

module DivToFloat:
  def Int::C#(/)(self, other):
    Float::from_int(self) / Float::from_int(other)
  end
  export Int::C#(/)
end
assert(3 / 2 == 1)
begin:
  open DivToFloat
  assert(3 / 2 == 1.5)
end
assert(3 / 2 == 1)

モジュールをopenすることで,モジュールからエクスポートされている定義を 任意のスコープにインポートすることが可能です.

その他の特徴についてはサンプルプログラムを 読んでいただくとなんとなくわかると思います. そのうちより詳しい解説を載せる予定です.

また1月に開催されるプログラミング・シンポジウムでは, 「環境にメソッドを直接格納する新しいオブジェクトシステムの提案」というタイトルで Suzuのオブジェクトシステムについて発表します.