Tagless Final DSL 吉吉 吉( @_yyu_ ) 1
1
Tagless Final DSL
吉村 優( @_yyu_ )
注意• この発表には Free モナドが含まれています• Free モナドを初めて聞く方は、自己紹介など
のスキにググっておくと、スムーズです• このスライドの Scala のようなプログラム言語
は、スペースの都合で省略されて部分があるので、実際には動きませんo https://github.com/yoshimuraYuu/DIwithTaglessFinal
2
自己紹介• Twitter
https://twitter.com/_yyu_
• GitHubhttps://github.com/yoshimuraYuu
• Qiitahttp://qiita.com/yyu
関数型言語( Scala, OCaml )、暗号、ときどき組版など
3
DSL とは?“DSL は一種類のタスクをうまく実行するこ
とに集中した言語である„Wikipedia
主な DSL としてo SQLo Yacco Make
4
Embedded DSL“ プログラム言語の内部に書かれた DSL のこ
と。 EDSL とも言う„主な EDSL として
o LINQo jQuery
5
EDSL を作る• Twitter を操作する Scala の EDSL
• まずは Free モナドによる伝統的なアプローチで作成
• 次に Tagless Final による実装
6
完成予定図• 指定したスクリーンネームのユーザー情報を取得
o存在すればexistとツイートoそうでなければnot existとツイート
val dsl = fetch( "_yyu_", res => if (res.status == 200) update("exist") else update("not exist"))runTwitter(dsl)
7
代数的データ型sealed trait Twitter[A]
case class Fetch[A](sn: String, next: WSResponse => A) extends Twitter[A]
case class Update[A](status: String, next: A) extends Twitter[A]
• sn はスクリーンネーム• next は次の処理を入れておくための場所• WSResponse は HTTP のレスポンスを表わす型
8
Twitter をファンクタへimplicit val tf = new Functor[Twitter] { def map[A, B](a: Twitter[A])(f: A => B) = a match { case Fetch(sn, next) =>
Fetch(sn, x => f(next(x))) case Update(status, next) =>
Update(status, f(next)) }}
• Free モナドを使うための準備
9
EDSL の定義def fetch[A](sn: String, f: WSResponse => Free[Twitter, A]): Free[Twitter, A] = More(Fetch(sn, f))
def update(s: String): Free[Twitter, Unit]= More(Update(status, Done()))
• Twitter をファンクタにしたので、 Free モナドを使うことができる
• Free モナドで Twitter[Twitter[A]] のような型を回避
10
インタープリターdef runTwitter[A](dsl: Free[Twitter, A]): Unit = dsl match { case Done(a) => () case More(Fetch(screenName, next)) => val fws = fetchUserByScreenName(screenName) runTwitter(next(fws), env) case More(Update(status, next)) => updateStatus(status) runTwitter(next, env) }
• 数珠繋ぎになった DSL を順に実行
具体的な実装
具体的な実装
11
完成val dsl = fetch( "_yyu_", res => if (res.status == 200) update("exist") else update("not exist"))runTwitter(dsl)
12
Taglees Final Approach
13
Tagless FinalEDSL をつくるための手法
• Free モナドを使った手法との違いoGADT (一般化代数的データ型)が不要oHOAS (高階抽象構文)が使えるoExpression Problem の回避o型付けをホスト言語に帰着できる
14
インターフェースDSL のインターフェースを型クラスとして提供
case class Twitter[A](v: A)
object Twitter { def twitter[A](a: A): Twitter[A] = twitter(a)}
trait TwitterSYM[R[_]] { def string(str: String): R[String] def fetch(sn: R[String]): R[WSResponse] def getSN(res: R[WSResponse]): R[String] def update(status: R[String]): R[String]}
15
インスタンスDSL を型クラスのインスタンスとして実装
implicit val twitterSYMInterpreter = new TwitterSYM[Twitter] { def getSN(res: Twitter[WSResponse]) = val raw = res.v twitter(
( raw.json \ "screen_name").as[String] ) }
16
実装DSL を型クラスのインスタンスとして実装
def getSN (res: Twitter[WSResponse]) (implicit T: TwitterSYM[Twitter]) = T.getSN(res)
17
Tagless Final DSL普通の関数のように呼び出し
getSN( fetch(string("_yyu_"))).v
18
拡張インターフェースが型クラスなので拡張が容易
// 新しい型クラスtrait DeleteSYM[R[_]] { def delete(id: R[String]): R[Boolean]}
19
拡張インターフェースが型クラスなので拡張が容易
// 実装implicit val deleteInterpreter = new DeleteSYM[Twitter] { def delete(id: Twitter[String]): Twitter[Boolean] =
??? }
def delete(id: Twitter[String]) (implicit T: DeleteSYM[Twitter]) = T.delete(id)
20
HOAS次のような let 構文を EDSL に入れたい
let a = string("_yyu_") inlet b = fetch(a) in let c = getScreeName(b) in let d = update(c) in delete(d)
このような変数の管理を実装するのは大変
スクリーンネームを代入
ユーザーの情報を取得取得したユーザーの
スクリーンネームを取得
スクリーンネームをツイート ツイートを削除
21
インターフェース変数の束縛をホスト言語に任せる
trait LetInSYM[R[_]] { def let[A, B](a: => R[A])(l: R[A => B]): R[B] def in[A, B](a: R[A] => R[B]): R[A => B]}
22
実装def let[A, B](a: => Twitter[A])(tf: Twitter[A => B]): Twitter[B] = tf.v(a.v)
def in[A, B](f: Twitter[A] => Twitter[B]): Twitter[A => B] = { twitter( (x: Twitter[A]) => f(x) )}
23
完成let (string("_yyu_")) (in (a =>let (fetch(a)) (in (b =>let (getScreeName(b)) (in (c =>let (update(c)) (in (d => delete(d)))))))))
• 括弧の数が少々えぐい……
24
まとめ• Tagless Final は Free モナドを使ったアプロー
チと同じものを表現できる• HOAS や型付けの帰着など、上手く使えば Free
モナドのアプローチよりも高い表現力が得られる
• しかし表現力の高い DSL はもはやL ( Language )という議論もある
25
おわり
26