tagless final dsl

26
Tagless Final DSL 吉吉 吉@_yyu_ 1

Upload: yyu

Post on 24-Jan-2017

917 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Tagless Final DSL

1

Tagless Final DSL

吉村 優( @_yyu_ )

Page 2: Tagless Final DSL

注意• この発表には Free モナドが含まれています• Free モナドを初めて聞く方は、自己紹介など

のスキにググっておくと、スムーズです• このスライドの Scala のようなプログラム言語

は、スペースの都合で省略されて部分があるので、実際には動きませんo https://github.com/yoshimuraYuu/DIwithTaglessFinal

2

Page 3: Tagless Final DSL

自己紹介• Twitter

https://twitter.com/_yyu_

• GitHubhttps://github.com/yoshimuraYuu

• Qiitahttp://qiita.com/yyu

関数型言語( Scala, OCaml )、暗号、ときどき組版など

3

Page 4: Tagless Final DSL

DSL とは?“DSL は一種類のタスクをうまく実行するこ

とに集中した言語である„Wikipedia

主な DSL としてo SQLo Yacco Make

4

Page 5: Tagless Final DSL

Embedded DSL“ プログラム言語の内部に書かれた DSL のこ

と。 EDSL とも言う„主な EDSL として

o LINQo jQuery

5

Page 6: Tagless Final DSL

EDSL を作る• Twitter を操作する Scala の EDSL

• まずは Free モナドによる伝統的なアプローチで作成

• 次に Tagless Final による実装

6

Page 7: Tagless Final DSL

完成予定図• 指定したスクリーンネームのユーザー情報を取得

o存在すればexistとツイートoそうでなければnot existとツイート

val dsl = fetch( "_yyu_", res => if (res.status == 200) update("exist") else update("not exist"))runTwitter(dsl)

7

Page 8: Tagless Final DSL

代数的データ型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

Page 9: Tagless Final DSL

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

Page 10: Tagless Final DSL

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

Page 11: Tagless Final DSL

インタープリター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

Page 12: Tagless Final DSL

完成val dsl = fetch( "_yyu_", res => if (res.status == 200) update("exist") else update("not exist"))runTwitter(dsl)

12

Page 13: Tagless Final DSL

Taglees Final Approach

13

Page 14: Tagless Final DSL

Tagless FinalEDSL をつくるための手法

• Free モナドを使った手法との違いoGADT (一般化代数的データ型)が不要oHOAS (高階抽象構文)が使えるoExpression Problem の回避o型付けをホスト言語に帰着できる

14

Page 15: Tagless Final DSL

インターフェース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

Page 16: Tagless Final DSL

インスタンスDSL を型クラスのインスタンスとして実装

implicit val twitterSYMInterpreter = new TwitterSYM[Twitter] { def getSN(res: Twitter[WSResponse]) = val raw = res.v twitter(

( raw.json \ "screen_name").as[String] ) }

16

Page 17: Tagless Final DSL

実装DSL を型クラスのインスタンスとして実装

def getSN (res: Twitter[WSResponse]) (implicit T: TwitterSYM[Twitter]) = T.getSN(res)

17

Page 18: Tagless Final DSL

Tagless Final DSL普通の関数のように呼び出し

getSN( fetch(string("_yyu_"))).v

18

Page 19: Tagless Final DSL

拡張インターフェースが型クラスなので拡張が容易

// 新しい型クラスtrait DeleteSYM[R[_]] { def delete(id: R[String]): R[Boolean]}

19

Page 20: Tagless Final DSL

拡張インターフェースが型クラスなので拡張が容易

// 実装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

Page 21: Tagless Final DSL

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

Page 22: Tagless Final DSL

インターフェース変数の束縛をホスト言語に任せる

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

Page 23: Tagless Final DSL

実装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

Page 24: Tagless Final DSL

完成let (string("_yyu_")) (in (a =>let (fetch(a)) (in (b =>let (getScreeName(b)) (in (c =>let (update(c)) (in (d => delete(d)))))))))

• 括弧の数が少々えぐい……

24

Page 25: Tagless Final DSL

まとめ• Tagless Final は Free モナドを使ったアプロー

チと同じものを表現できる• HOAS や型付けの帰着など、上手く使えば Free

モナドのアプローチよりも高い表現力が得られる

• しかし表現力の高い DSL はもはやL ( Language )という議論もある

25

Page 26: Tagless Final DSL

おわり

26