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は環境にメソッドを直接格納することにより,メソッドの集合であるトレイトの操作を他の言語でも馴染み深いモジュールの操作の中に組み込むことが可能となっています.
柔軟なメソッド定義が可能なプログラミング言語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のオブジェクトシステムについて発表します.