Интерпретирование языков с помощью free-монад

70
Интерпретирование языков с помощью Free-монад https://twitter.com/ZhekaKozlov

Upload: john-kozlov

Post on 20-Jul-2015

121 views

Category:

Science


0 download

TRANSCRIPT

Page 1: Интерпретирование языков с помощью Free-монад

Интерпретирование языков с помощью Free-монад

https://twitter.com/ZhekaKozlov

Page 2: Интерпретирование языков с помощью Free-монад

Пример 1: Console I/O

echo ∷ IO () echo = do s ← getLine putStrLn s

Page 3: Интерпретирование языков с помощью Free-монад

Проблемы

Как убедиться, что echo вызывает только функции работы с консолью?

Мы не можем «заглянуть внутрь» IO и

проконтролировать, что не происходят нежелательные эффекты!

Page 4: Интерпретирование языков с помощью Free-монад

Нежелательный эффект

echo ∷ IO () echo = do s ← getLine launchMissile putStrLn s launchMissile ∷ IO () launchMissile = …

Page 5: Интерпретирование языков с помощью Free-монад

Калькулятор

calculator ∷ IO () calculator = do x ← getLine y ← getLine putStrLn (show (read x + read y))

Page 6: Интерпретирование языков с помощью Free-монад

Проблемы

Как протестировать calculator? сalculator возвращает IO() – мы не

можем протестировать ()!

Page 7: Интерпретирование языков с помощью Free-монад

Легко допустить ошибку

calculator ∷ IO () calculator = do x ← getLine y ← getLine putStrLn (show (read x + read x))

Page 8: Интерпретирование языков с помощью Free-монад

Решение?

В этой книге!

Page 9: Интерпретирование языков с помощью Free-монад

1994 год

Page 10: Интерпретирование языков с помощью Free-монад

1994 год

RIP

Page 11: Интерпретирование языков с помощью Free-монад

Шаблон Интепретатор

«… определяет грамматику простого языка, представляет предложения на этом языке и интерпретирует их»

Page 12: Интерпретирование языков с помощью Free-монад

Пример грамматики

expression ← literal | alternation | sequence | repetition | '(' expression ')' alternation ← expression '|' expression sequence ← expression '&' expression

repetition ← expression '*'

literal ← ['a'..'z']*

Page 13: Интерпретирование языков с помощью Free-монад

Диаграмма классов

RegularExpression

interpret()

LiteralExpression

interpret()

literal

SequenceExpression

interpret()

expression1

expression2

RepetitionExpression

interpret()

expression

AlternateExpression

interpret()

expression1

expression2

Page 14: Интерпретирование языков с помощью Free-монад

Диаграмма классов

Expr

interpret()

Literal

interpret()

literal

And

interpret()

expression1

expression2

Many

interpret()

expression

Or

interpret()

expression1

expression2

Page 15: Интерпретирование языков с помощью Free-монад

ADT!

data Expr = Literal String | And Expr Expr | Or Expr Expr | Many Expr

Page 16: Интерпретирование языков с помощью Free-монад

Интерпретатор

interpret ∷ String → Expr → (Bool, String) interpret s (Literal lit) | lit `isPrefixOf` s = (True, drop (length lit) s) interpret s (And e1 e2) | (True, tail) ← interpret s e1 = interpret tail e2 interpret s (Or e1 e2) | (ok, tail) ← interpret s e1 = if ok then (True, tail) else interpret s e2 interpret s (Many e) | (ok, tail) ← interpret s e = if ok then interpret tail (Many e) else (True, s) interpret s _ = (False, s)

Page 17: Интерпретирование языков с помощью Free-монад

raining & (dogs | cats)*

And

expression1 expression2

Literal

‘raining’

Or

expression1 expression2

Many

expression

Literal

‘dogs’

Literal

‘cats’

Page 18: Интерпретирование языков с помощью Free-монад

raining & (dogs | cats)*

ex = And (Literal "raining") (Many (Or (Literal "dogs") (Literal "cats")))

Page 19: Интерпретирование языков с помощью Free-монад

raining & (dogs | cats)*

ex = And (Literal "raining") (Many (Or (Literal "dogs") (Literal "cats"))) > interpret "rainingdogscats1" ex (True,"1") > interpret "abc" ex (False,"abc")

Page 20: Интерпретирование языков с помощью Free-монад

Таким образом

• Шаблон Интерпретатор определяет грамматику:

data Expr = Literal String | And Expr Expr | Or Expr Expr | Many Expr

And (Literal "raining") (Many (Or (Literal "dogs") (Literal "cats")))

• … представляет предложения на этом языке:

• … и интерпретирует их:

interpret = …

Page 21: Интерпретирование языков с помощью Free-монад

Console I/O

Можно ли применить шаблон Интерпретатор для языка консольного ввода-вывода?

Page 22: Интерпретирование языков с помощью Free-монад

Console I/O

Можно ли применить шаблон Интерпретатор для языка консольного ввода-вывода?

Можно

Page 23: Интерпретирование языков с помощью Free-монад

Диаграмма классов ADT

Command

PrintLine

String

ReadLine

data Command = PrintLine String | ReadLine

Page 24: Интерпретирование языков с помощью Free-монад

Диаграмма классов ADT

Command

PrintLine

String Command

ReadLine

data Command = PrintLine String Command | ReadLine (String → Command)

String → Command

Page 25: Интерпретирование языков с помощью Free-монад

Диаграмма классов ADT

Command a

PrintLine

String a

ReadLine

data Command a = PrintLine String a | ReadLine (String → a)

String → a

Page 26: Интерпретирование языков с помощью Free-монад

echo

data Command a = PrintLine String a | ReadLine (String → a) echo = ReadLine (\s → PrintLine s ())

Page 27: Интерпретирование языков с помощью Free-монад

calculator

data Command a = PrintLine String a | ReadLine (String → a) calculator = ReadLine (\x → ReadLine (\y → PrintLine (show (read x + read y)) ()))

Page 28: Интерпретирование языков с помощью Free-монад

Проблема

> :type echo echo ∷ Command (Command ()) > :type calculator calculator ∷ Command (Command (Command ()))

Page 29: Интерпретирование языков с помощью Free-монад

Проблема

> :type echo echo ∷ Command (Command ()) > :type calculator calculator ∷ Command (Command (Command ()))

Как написать интерпретатор для Command (Command (Command (…)))?

Page 30: Интерпретирование языков с помощью Free-монад

Free

data Free f a = Return a | Free (f (Free f a))

Page 31: Интерпретирование языков с помощью Free-монад

echo

data Free f a = Return a | Free (f (Free f a)) echo = Free (ReadLine (\s → Free (PrintLine s (Return ()))))

Page 32: Интерпретирование языков с помощью Free-монад

calculator

data Free f a = Return a | Free (f (Free f a)) calculator = Free (ReadLine (\x -> Free (ReadLine (\y -> Free (PrintLine (show (read x + read y)) (Return ()))))))

Page 33: Интерпретирование языков с помощью Free-монад

Типы

> :type echo echo ∷ Free Command () > :type calculator calculator ∷ Free Command ()

Page 34: Интерпретирование языков с помощью Free-монад

Громоздко

calculator = Free (ReadLine (\x -> Free (ReadLine (\y -> Free (PrintLine (show (read x + read y)) (Return ()))))))

Page 35: Интерпретирование языков с помощью Free-монад

Free f является монадой!

Утверждение: если f является функтором, то Free f является монадой.

data Free f a = Return a | Free (f (Free f a))

Page 36: Интерпретирование языков с помощью Free-монад

Free f является монадой!

Утверждение: если f является функтором, то Free f является монадой.

data Free f a = Return a | Free (f (Free f a)) instance Functor f ⇒ Monad (Free f) where return = Return (Return a) >>= g = g a (Free f) >>= g = Free (fmap (>>= g) f)

Page 37: Интерпретирование языков с помощью Free-монад

Command является функтором

data Command a = PrintLine String a | ReadLine (String → a) deriving Functor

Page 38: Интерпретирование языков с помощью Free-монад

Command является функтором

data Command a = PrintLine String a | ReadLine (String → a) deriving Functor type Console = Free Command

Page 39: Интерпретирование языков с помощью Free-монад

Command является функтором

data Command a = PrintLine String a | ReadLine (String → a) deriving Functor type Console = Free Command printLine ∷ String → Console () printLine s = Free (PrintLine s (Return ())) readLine ∷ Console String readLine = Free (ReadLine (\s → Return s))

Page 40: Интерпретирование языков с помощью Free-монад

echo

echo ∷ Console () echo = do s ← readLine printLine s

Page 41: Интерпретирование языков с помощью Free-монад

echo

echo ∷ Console () echo = do s ← readLine launchMissile printLine s

Couldn't match type `IO' with `Console'

Page 42: Интерпретирование языков с помощью Free-монад

calculator

calculator ∷ Console () calculator = do x ← readLine y ← readLine printLine (show (read x + read y))

Page 43: Интерпретирование языков с помощью Free-монад

Пишем интерпретатор

runConsole ∷ Console a → IO a runConsole (Return a) = return a runConsole (Free command) = case command of PrintLine s a → do putStrLn s runConsole a ReadLine after → do s ← getLine runConsole (after s)

Page 44: Интерпретирование языков с помощью Free-монад

Запускаем интерпретатор

> runConsole echo Hello Hello > runConsole calculator 3 4 7

Page 45: Интерпретирование языков с помощью Free-монад

2-й интерпретатор (для теста)

Console a

runConsole testConsole

Page 46: Интерпретирование языков с помощью Free-монад

Тест

testConsole ∷ Console a → [String] → [String] testConsole (Return _) _ = [] testConsole (Free command) inputs = case command of PrintLine s a → s : testConsole a inputs ReadLine after → testConsole (after (head inputs)) (tail inputs)

Page 47: Интерпретирование языков с помощью Free-монад

Запускаем тест

> :m + Test.QuickCheck > quickCheck (\s → testConsole echo [s] == [s]) +++ OK, passed 100 tests

Page 48: Интерпретирование языков с помощью Free-монад

Запускаем тест

> quickCheck (\x y → testConsole calculator [show x, show y] == [show (x + y)]) +++ OK, passed 100 tests

Page 49: Интерпретирование языков с помощью Free-монад

3-й интерпретатор (вывод в файл)

Console a

runConsole testConsole

runConsoleFile

Page 50: Интерпретирование языков с помощью Free-монад

Таким образом

• Шаблон Интепретатор определяет грамматику:

data Command a = PrintLine String a | ReadLine (String → a) deriving Functor

do s ← readLine printLine s

• … представляет предложения на этом языке:

• … и интерпретирует их:

runConsole = … testConsole = …

Page 51: Интерпретирование языков с помощью Free-монад

Пример 2: Key-Value Store

data KVSCommand a = Put String String a | Get String (String → a) | Delete String a deriving Functor type KVS = Free KVSCommand

Page 52: Интерпретирование языков с помощью Free-монад

Пример 2: Key-Value Store

put ∷ String → String → KVS () put k v = Free (Put k v (Return ())) get ∷ String → KVS String get k = Free (Get k Return) del ∷ String → KVS () del k = Free (Delete k (Return ()))

Page 53: Интерпретирование языков с помощью Free-монад

Пример 2: Key-Value Store

put ∷ String → String → KVS () put k v = Free (Put k v (Return ())) get ∷ String → KVS String get k = Free (Get k Return) del ∷ String → KVS () del k = Free (Delete k (Return ())) modify ∷ String → (String → String) → KVS () modify k f = do v ← get k put k (f v)

Page 54: Интерпретирование языков с помощью Free-монад

Интерпретатор KVS (NoSQL)

runNoSQL ∷ KVS a → Connection → IO a runNoSQL = …

Page 55: Интерпретирование языков с помощью Free-монад

Интерпретатор KVS (NoSQL)

runNoSQL ∷ KVS a → Connection → IO a runNoSQL = … kvs ∷ KVS String kvs = do put "firstName" "John" put "lastName" "Doe" modify "age" (show.(+1).read) get "salary" main = do conn ← getConnection runNoSQL kvs conn

Page 56: Интерпретирование языков с помощью Free-монад

Интерпретатор KVS (Map)

import Data.Map runMap ∷ KVS a → Map String String → Map String String runMap (Return _) map = map runMap (Free f) map = case f of Put k v a → runMap a (insert k v map) Get k after → runMap (after (map ! k)) map Delete k a → runMap a (delete k map)

Page 57: Интерпретирование языков с помощью Free-монад

Эффективный интерпретатор

do put "key1" "value1" put "key2" "value2" put "key3" "value3"

"key1" ,"value1"

"key2" ,"value2"

"key3" ,"value3"

Page 58: Интерпретирование языков с помощью Free-монад

Эффективный интерпретатор

do put "key1" "value1" put "key2" "value2" put "key3" "value3"

"key1" ,"value1"

"key2" ,"value2"

"key3" ,"value3"

["key1" ,"value1", "key2" ,"value2", "key3" ,"value3"]

Page 59: Интерпретирование языков с помощью Free-монад

Пример 3: загрузка твитов

import Data.ByteString data Tweet = Tweet { id ∷ Integer, userName ∷ String, text ∷ String } deriving Show data User = User { name ∷ String, photo ∷ ByteString } deriving Show fetchTweets ∷ String → Int → IO [Tweet] fetchUser ∷ String → IO User

Page 60: Интерпретирование языков с помощью Free-монад

Twitter API

fetchTweets ∷ String → Int → IO [Tweet] fetchTweets "odersky" _ = do print ("Fetching tweets for @odersky") return [Tweet 1 "odersky" "Scala!", Tweet 2 "bos31337" "Haskell!", Tweet 3 "odersky" "Good morning"]

fetchUser ∷ String → IO User fetchUser name = do print ("Fetching user profile for @" ++ name) return $ case name of

"odersky" → User "odersky" (pack [1,2,3]) "bos31337" → User "bos31337" (pack [4,5])

Page 61: Интерпретирование языков с помощью Free-монад

Хотим достать твиты с фото

fetchTweetsWithPhotos ∷ String → Int → IO [(Tweet, ByteString)] fetchTweetsWithPhotos name limit = do tweets ← fetchTweets name limit forM tweets (\t → do user ← fetchUser (userName t) return (t, image user))

Page 62: Интерпретирование языков с помощью Free-монад

Хотим достать твиты с фото

fetchTweetsWithPhotos ∷ String → Int → IO [(Tweet, ByteString)] fetchTweetsWithPhotos name limit = do tweets ← fetchTweets name limit forM tweets (\t → do user ← fetchUser (userName t) return (t, image user)) > fetchTweetsWithPhotos "odersky" 10 "Fetching tweets for odersky" "Fetching user profile for odersky" "Fetching user profile for bos31337" "Fetching user profile for odersky"

Page 63: Интерпретирование языков с помощью Free-монад

Хотим достать твиты с фото

fetchTweetsWithPhotos ∷ String → Int → IO [(Tweet, ByteString)] fetchTweetsWithPhotos name limit = do tweets ← fetchTweets name limit forM tweets (\t → do user ← fetchUser (userName t) return (t, image user)) > fetchTweetsWithPhotos "odersky" 10 "Fetching tweets for odersky" "Fetching user profile for odersky" "Fetching user profile for bos31337" "Fetching user profile for odersky"

2 запроса!

Page 64: Интерпретирование языков с помощью Free-монад

Решение

data TwitterCommand a = GetTweets String Int ([Tweet] → a) | GetUser String (User → a) deriving Functor type Twitter = Free TwitterCommand

Page 65: Интерпретирование языков с помощью Free-монад

Решение

data TwitterCommand a = GetTweets String Int ([Tweet] → a) | GetUser String (User → a) deriving Functor type Twitter = Free TwitterCommand getTweets ∷ String → Int → Twitter [Tweet] getTweets name limit = Free (GetTweets name limit Return) getUser ∷ String → Twitter User getUser name = Free (GetUser name Return)

Page 66: Интерпретирование языков с помощью Free-монад

Кэширующий интерпретатор

runTwitter ∷ Twitter a → Map String User → IO a runTwitter (Return a) _ = return a runTwitter (Free f) cache = case f of GetTweets s limit after → do tweets ← fetchTweets s limit runTwitter (after tweets) cache GetUser name after → do user ← if member name cache then return (cache ! name) else fetchUser name runTwitter (after user) (insert name user cache)

Кэш фото

Page 67: Интерпретирование языков с помощью Free-монад

Хотим достать твиты с фото

getTweetsWithPhotos ∷ String → Int → Twitter [(Tweet, ByteString)] getTweetsWithPhotos name limit = do tweets ← getTweets name limit forM tweets (\t → do user ← getUser (userName t) return (t, photo user)) > runTwitter (getTweetsWithPhotos "odersky" 10) empty "Fetching tweets for odersky" "Fetching user profile for odersky" "Fetching user profile for bos31337"

1 запрос

Page 68: Интерпретирование языков с помощью Free-монад

Ещё

При желании можно написать интерпретатор, который распараллеливает запросы.

Page 69: Интерпретирование языков с помощью Free-монад

Существующие библиотеки

Haskell: • https://github.com/facebook/Haxl • http://hackage.haskell.org/package/CouchDB Scala: • Twitter Stitch (Scala, not open sourced) • https://github.com/MonsantoCo/stoop

Page 70: Интерпретирование языков с помощью Free-монад

Вопросы?