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

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は環境にメソッドを直接格納することにより,メソッドの集合であるトレイトの操作を他の言語でも馴染み深いモジュールの操作の中に組み込むことが可能となっています.