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)
上の例では,BankAccount
とStockAccount
に共通する操作を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
をローカルに定義しています(限定継続オペレータであるshift
とreset
を使用しています).
Suzuのモジュールは他にも,include
キーワードによって合成したり,except
キーワードによってメソッドや変数を取り除いたりできます.
Suzuは環境にメソッドを直接格納することにより,メソッドの集合であるトレイトの操作を他の言語でも馴染み深いモジュールの操作の中に組み込むことが可能となっています.