{
use dsl::*;
let conn = get_conn();
check_if(
&conn,
// 権限チェックのDSL
operator.is_readable().is_writable().to(device),
)?;
// do update
Ok(device.clone())
}
```
===
# モデリング
------------
``` rust
// これを実装しているものがDSLになる気持ち
trait Precondition {
fn check(&self, conn: &DbConn) -> Result<()>;
}
struct And(P, Q);
impl
Precondition for And
where
P: Precondition,
Q: Precondition,
{
fn check(&self, conn: &DbConn) -> Result<()> {
self.0.check(conn).and_then(|()| self.1.check(conn))
}
}
struct IsReadable(A, B);
struct IsWritable(A, B);
impl Precondition for IsReadable {
fn check(&self, _conn: &DbConn) -> Result<()> {
// do ckeck
Ok(())
}
}
impl Precondition for IsWritable {
fn check(&self, _conn: &DbConn) -> Result<()> {
// do ckeck
Err(Error::WritePrivilege)
}
}
```
===
# DSLの構築
-----------
``` rust
mod dsl {
use super::*;
pub struct IncompleteIsReadable(T);
pub trait IsReadableDsl {
type Out;
fn is_readable(self) -> Self::Out;
}
impl IsReadableDsl for User {
type Out = IncompleteIsReadable;
fn is_readable(self) -> Self::Out {
IncompleteIsReadable(self)
}
}
// 長いので略
}
```
===
# 内部DSL
---------
* [コード全体](https://gist.github.com/KeenS/d8ef8c95110742d31c74c750ed456ecb)
* 正道
* トレイトをうまく使う
+ メソッド記法でそれっぽく
+ 演算子オーバーロードはあまり悪用しない
* DSLはあくまで略記のための手法
+ 何がしたいかよくモデリングする
* モデルと記法分離することで内部実装を変更できたりも
===
# 特殊化
-------
``` rust
impl Precondition for IsReadableWritable {
fn check(&self, _conn: &DbConn) -> Result<()> {
// 本当はAnd, IsWritable<_, _>>でも可能
// DBアクセスを減らすために特殊化
Err(Error::WritePrivilege)
}
}
mod dsl {
impl ToDsl for IncompleteIsReadableWritable {
type Out = IsReadableWritable;
fn to(self, to: Device) -> Self::Out {
// ユーザインタフェースを(見た目レベルでは)変えずに内部実装を変更可能
// And(IsReadable(self, to), IsWritable(self, to))
IsReadableWritable(self.0, to)
}
}
}
```
===
# 諸刃の剣のマクロDSL
===
# API定義
---------
``` rust
{
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PathParameters {
app_id: api::AppId,
}
app = app.route(
"/apps/{app_id}",
Method::GET,
| (req, path, sess):
(HttpRequest, Path, Session)|-> FutureResponse {
let _user: model::User = match sess.get::(SESSION_KEY) {
Ok(Some(user)) => user,
Ok(None) => return Box::new(Err(hoge).into_future()).responder(),
Err(e) => return Box::new(Err(e).into_future()).responder(),
};
let path = path.into_inner();
// 関心があるのはここ
service::api::find_app(&*ctx, path.app_id)
// and do more
let fut = fut.map(Into::into) .from_err();
let fut: Box> = Box::new(fut);
fut.responder()
});
}
```
===
# API定義
---------
``` rust
def_api!{
Method: GET,
// ルーティングと引数を同時記述
Path: { /apps/{app_id:api::AppId} },
Session,
Response: api::api::apps_::get::Response,
// この`path`に`app_id`が入る。
// 引数もマクロの書き方で増減する
Handler: |ctx, path, _user| {
service::api::find_app(&*ctx, path.app_id)
// and do more
}
};
```
===
# マクロDSL
-------------
* マクロの実装は出せない
+ 出しても多分読めない
+ 合計300行くらいのマクロ群
* 可能なら使わない方が良い
+ マクロは第一級でない(関数の引数に渡せない)
* マクロにしかできないこともある
* arityの調整、シンボルから文字列の生成、構造体の定義の生成などなど
* まあまあトリッキー
+ [The Little Book of Rust Macros](https://danielkeep.github.io/tlborm/book/index.html)読んで
===
# マクロと型
------------
``` rust
macro_rules! take_tt {
($t:tt) => {
stringify!($t)
};
}
// エラー: `::std::string::String`はそのままだと複数のttになる
let _ = take_tt!(::std::string::String);
macro_rules! wrap_ty {
($t:ty) => {
take_tt!($t)
};
}
// OK: 一旦tyとしてパースすると1つのttになる
let _ = wrap_ty!(::std::string::String);
```
===
# マクロのCPS変換
---------
``` rust
macro_rules! id_ty {
($t:ty) => {
$t
};
}
// エラー: マクロの入れ子呼び出しはできない
let _ = take_tt!(id_ty!(::std::string::String));
macro_rules! id_ty_cps {
($t:ty, $callback:ident) => {
$callback!($t)
};
}
// OK: コールバックとして受け取ればよい
let _ = id_ty_cps!(::std::string::String, take_tt);
```
===
# みぞの鏡の外部DSL
> erised stra ehru oyt ube cafru oyt on wohsi -- The Mirror of Erised
===
# JSON Schema
-------------
``` json
{
"type":"object",
"properties": {
"id": {"type": "integer"},
"name": {"type": "string"}
}
"required": ["id", "name"]
}
```
===
===
# JSON Schema
-------------
略記できるツールを作った
```
struct {
id: integer,
name: string,
}
```
===
# 外部DSL
---------
* [KeenS/chema](https://github.com/KeenS/chema)
* ほしいものがなんでも手に入る
* 最後の手段
* 文字列からRustのデータ型を構成する
+ 今回はJSON SchemaがターゲットなのでJSONにダンプ
+ ある意味ではコンパイラ
* 最後の手段
* 開発支援ツールやエラーメッセージが壊滅的
* 最後の手段
===
# まとめ
--------
* DSLは色々なケースで役に立つよ
* 3種のDSLを使いこなそう
* 珠玉の内部DSL
+ 普段はこれを使おう
* 諸刃の剣のマクロDSL
+ デメリットをよく考えて使おう
* みぞの鏡の外部DSL
+ なんでも出来るけど溺れるな