Alloy Analyzer でOrphan Removal(を、もういちど。)

前回、写真共有サービスを想定して、@OneToMany(orphanRemoval=true) な
エンティティ群を定義してみました。その書き直し版です。

前回は、「状態をあらわすsig」を、書くと言っておきながら、
操作対象のインスタンス(=削除するUserとPhoto)のことだけを考えていました。
しかし、本来「状態」とは、操作対象でないインスタンスも含めて
(=RDBでいうと、関係するテーブルのレコードぜんぶ)表現していないと
いけないのでは思い直し、書き直しました。

module orphanRemoval
/* 基本のエンティティ ======================================================= */
sig User {
  photos: set Photo
}
sig Photo {
  owner: one User
}
// 所有者のいないPhotoは無い
fact {
  photos = ~owner
}
/* 状態管理用エンティティ =================================================== */
/*
■User削除前
*/
one sig PhotoRel_RightS {
  u: set User,
  p: some Photo,
  rel: p -> u
} {
  rel = p <: owner
}
fact {
  // ※このsigのフィールドはすべてのインスタンスを扱っていること
  all everyUser:User, everyPhoto:Photo, s:PhotoRel_RightS | 
    (everyUser in s.u) && (everyPhoto in s.p)
}
/*
■UserのPhotoだけ削除完了
*/ 
one sig PhotoRel_OrphanRemovedS { 
  delUser: set User,
  u'': set User,
  p'': some Photo,
  rel'': p'' some -> (u'' - delUser)
} { 
  rel'' = p'' <: owner
  // ※Photoを限定:deluserのphotsは無い。
  p'' = (u'' - delUser).photos
}
fact {
  all 
    before:PhotoRel_RightS, 
    process:PhotoRel_OrphanRemovedS, 
    after:PhotoRel_AllRemovedS | 
      // ※delUserを限定
      process.delUser = before.u - after.u' && 
        // ※Userを限定
        (process.u'' = before.u) 
}
/*
■User/Photo削除後
*/
one sig PhotoRel_AllRemovedS {
  u': some User,
  p': some Photo,
  rel': p' -> u'
} {
  rel' = p' <: owner
  // ※Photoを限定
  p' = u'.photos
}
fact {
  // ※Userを限定:削除されたUserが必ず存在すること
  all before:PhotoRel_RightS, after:PhotoRel_AllRemovedS | 
    (before.u - after.u') != none
}

エンティティの sigと、状態を表す sig 、そして、それらにまつわる fact はこんな感じです。
状態を表す sig は、より厳密になったと思います。

そして、削除の振る舞いの表現は、以下のようにしました。
各状態が持つ、Photo -> User 型フィールド同士を比較すればいいはずです。

/* 振る舞いの表現 =========================================================== */
/*
▼各状態のrelを比較して判定。
*/
pred deleteUser(
  rightS: PhotoRel_RightS, 
  orphanRemovedS: PhotoRel_OrphanRemovedS, 
  allRemovedS: PhotoRel_AllRemovedS, 
  userDeleted: User
) {
  after_OrphanRemoved[rightS, orphanRemovedS] and
  after_AllRemoved[rightS, allRemovedS, userDeleted]
}
pred after_OrphanRemoved(
  rightS'': PhotoRel_RightS, 
  orphanRemovedS'': PhotoRel_OrphanRemovedS
) {
  rightS''.rel - orphanRemovedS''.rel'' = orphanRemovedS''.delUser.photos <: owner
}
pred after_AllRemoved(
  rightS': PhotoRel_RightS, 
  allRemovedS': PhotoRel_AllRemovedS,
  userDeleted': User
) {
  rightS'.rel - allRemovedS'.rel' = userDeleted'.photos <: owner
}

/* 振る舞いのcheck ========================================================== */
check deleteUser {
  all user: User, 
    rightS: PhotoRel_RightS, 
    orphanRemovedS: PhotoRel_OrphanRemovedS, 	
    allRemovedS: PhotoRel_AllRemovedS | 
      // ※削除するUserを事後条件から特定 
      // TODO: これの else はどう表現するべきなのか?
      user = rightS.u - allRemovedS.u implies
        deleteUser[rightS, orphanRemovedS, allRemovedS, user]
}

事前状態 PhotoRel_RightS にも、事後状態 PhotoRel_AllRemovedS にも、
削除する(した)Userを特定する情報がありません。
それが正しいのかどうかわからないんですが、結果的にそうなりました。
この問題は、両者の差分(rightS.u - allRemovedS.u)をとることで、簡単に解決できるわけですが、
プログラマーの思考には、なんかしっくりこないやり方です。
事後の状態にあわせて引数を定義する・・・?
じゃあその定義外の引数については、どう考えたらいいんだろう?
あるいは、この定義に合わない「状態」(の組み合わせ)のほうを、除外していることになるんだろうか。
でもそれだったら、factで表現すべきだよな・・・?

そして前回同様、「状態をあらわすsig」の記述には手こずりました。
「状態」と、ひとくちに言っても、そこには、エンティティと/フィールドと/制約/
三者が絡むという複雑さがあるので、alloyの要領に慣れないうちは、
このように手こずるんだと思います。ただ、正しく書ければ、それがビジュアルに
検証できるというありがたさがあるので、それを信じて今後も頑張る。

それにしても、たった一対のOneToMany(OneToOne)を表現するだけでもこの記述量とは・・・
とてもじゃありませんが、一般的なWEB+RDBシステム開発現場で、
普段づかい出来るような代物ではありません。

これらの一連の制約の記述、自動生成できないかな。DSL的に。
alloy言語そのものを拡張するか、あるいは、
alloy4eclipse のように、IDEのプラットフォームに乗っかって、
コードスニペットとして自動生成するツールを作るか、、、

面倒なものに手を出してしまった。。。