第15回:モナディック設計の実践
~Ohamaにおけるモナド実装とその応用~
はじめに
ohamaフレームワークにおけるモナドの実装と、それを用いた横断的関心事の分離について解説します。
モナドの基本実装
// モナドの基本トレイト
pub trait Monad: Sized {
type Inner;
fn pure(value: Self::Inner) -> Self;
fn bind<B, F>(self, f: F) -> B
where
F: FnOnce(Self::Inner) -> B,
B: Monad;
}
// Resultモナド
pub struct ResultMonad<T, E> {
inner: Result<T, E>,
}
impl<T, E> Monad for ResultMonad<T, E> {
type Inner = T;
fn pure(value: T) -> Self {
Self {
inner: Ok(value)
}
}
fn bind<B, F>(self, f: F) -> B
where
F: FnOnce(T) -> B,
B: Monad,
{
match self.inner {
Ok(value) => f(value),
Err(e) => B::pure(e),
}
}
}
// Optionモナド
pub struct OptionMonad<T> {
inner: Option<T>,
}
impl<T> Monad for OptionMonad<T> {
type Inner = T;
fn pure(value: T) -> Self {
Self {
inner: Some(value)
}
}
fn bind<B, F>(self, f: F) -> B
where
F: FnOnce(T) -> B,
B: Monad,
{
match self.inner {
Some(value) => f(value),
None => B::pure(()),
}
}
}
カスタムモナドの作成
// ロギングモナド
pub struct LoggingMonad<T> {
value: T,
logs: Vec<String>,
}
impl<T> LoggingMonad<T> {
pub fn new(value: T) -> Self {
Self {
value,
logs: Vec::new(),
}
}
pub fn log(&mut self, message: &str) {
self.logs.push(message.to_string());
}
pub fn get_logs(&self) -> &[String] {
&self.logs
}
}
impl<T> Monad for LoggingMonad<T> {
type Inner = T;
fn pure(value: T) -> Self {
Self::new(value)
}
fn bind<B, F>(mut self, f: F) -> B
where
F: FnOnce(T) -> B,
B: Monad,
{
let result = f(self.value);
// ログの転送
if let Some(mut new_logs) = result.get_logs_mut() {
new_logs.extend(self.logs);
}
result
}
}
// 状態モナド
pub struct StateMonad<S, T> {
run_state: Box<dyn FnOnce(S) -> (T, S)>,
}
impl<S, T> StateMonad<S, T> {
pub fn new<F>(f: F) -> Self
where
F: FnOnce(S) -> (T, S) + 'static,
{
Self {
run_state: Box::new(f),
}
}
pub fn run(&self, initial_state: S) -> (T, S) {
(self.run_state)(initial_state)
}
}
impl<S, T> Monad for StateMonad<S, T> {
type Inner = T;
fn pure(value: T) -> Self {
Self::new(move |s| (value, s))
}
fn bind<B, F>(self, f: F) -> B
where
F: FnOnce(T) -> B,
B: Monad,
{
let new_state = move |s: S| {
let (a, new_s) = (self.run_state)(s);
f(a).run(new_s)
};
StateMonad::new(new_state)
}
}
モナド変換子
// モナド変換子の基本トレイト
pub trait MonadTransformer {
type BaseMonad: Monad;
type Inner;
fn lift(base: Self::BaseMonad) -> Self;
fn lower(self) -> Self::BaseMonad;
}
// ResultT変換子
pub struct ResultT<M, T, E>
where
M: Monad,
{
inner: M::Inner<Result<T, E>>,
}
impl<M, T, E> MonadTransformer for ResultT<M, T, E>
where
M: Monad,
{
type BaseMonad = M;
type Inner = Result<T, E>;
fn lift(base: M) -> Self {
Self { inner: base }
}
fn lower(self) -> M {
self.inner
}
}
// モナドスタックの例
pub type AppMonad<T> = ResultT<LoggingMonad<StateMonad<AppState, T>>, T, Error>;
impl<T> AppMonad<T> {
pub fn run(&self, initial_state: AppState) -> (Result<T, Error>, Vec<String>, AppState) {
let (result, logs, final_state) = self.lower()
.run(initial_state);
(result, logs.get_logs().to_vec(), final_state)
}
}
実装例:トランザクションモナドの実装
// トランザクションモナド
pub struct TransactionMonad<T> {
operations: Vec<Box<dyn Operation>>,
result: T,
}
impl<T> TransactionMonad<T> {
pub async fn commit(&self) -> Result<T> {
// トランザクションの開始
let mut transaction = Transaction::begin().await?;
// 操作の実行
for operation in &self.operations {
if let Err(e) = operation.execute(&mut transaction).await {
transaction.rollback().await?;
return Err(e);
}
}
// コミット
transaction.commit().await?;
Ok(self.result.clone())
}
}
impl<T> Monad for TransactionMonad<T> {
type Inner = T;
fn pure(value: T) -> Self {
Self {
operations: Vec::new(),
result: value,
}
}
fn bind<B, F>(mut self, f: F) -> B
where
F: FnOnce(T) -> B,
B: Monad,
{
let result = f(self.result);
// 操作の結合
if let Some(mut new_ops) = result.get_operations_mut() {
new_ops.extend(self.operations);
}
result
}
}
// 使用例
async fn perform_transaction() -> Result<()> {
let transaction = TransactionMonad::pure(())
.bind(|_| create_user("John"))
.bind(|user| create_account(user.id))
.bind(|account| deposit_money(account.id, 1000.0));
transaction.commit().await
}
// 実際の使用例
pub struct UserService {
transaction_manager: TransactionManager,
}
impl UserService {
pub async fn create_user_with_account(&self, name: String) -> Result<User> {
let transaction = TransactionMonad::pure(name)
.bind(|name| self.create_user(name))
.bind(|user| {
self.create_account(user.id)
.map(|account| (user, account))
})
.bind(|(user, account)| {
self.link_user_account(user.id, account.id)
.map(|_| user)
});
transaction.commit().await
}
}
今回のまとめ
- 基本的なモナドの実装方法
- カスタムモナドの作成と利用
- モナド変換子による機能の組み合わせ
- 実用的なトランザクションモナドの実装
次回予告
第16回では、unsafe-cast管理について解説します。安全性保証メカニズムと型変換の追跡について詳しく見ていきます。
参考資料
- Functional Programming in Rust
- Monad Transformers Step by Step
- Type-Safe Database Transactions
- Advanced Rust Programming