3. Проблемы
Как убедиться, что echo вызывает
только функции работы с консолью?
Мы не можем «заглянуть внутрь» IO и
проконтролировать, что не
происходят нежелательные
эффекты!
4. Нежелательный эффект
echo ∷ IO ()
echo = do
s ← getLine
launchMissile
putStrLn s
launchMissile ∷ IO ()
launchMissile = …
16. Интерпретатор
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)
17. raining & (dogs | cats)*
And
expression1
expression2
Literal
‘raining’
Or
expression1
expression2
Many
expression
Literal
‘dogs’
Literal
‘cats’
18. raining & (dogs | cats)*
ex = And (Literal "raining")
(Many (Or (Literal "dogs")
(Literal "cats")))
19. raining & (dogs | cats)*
ex = And (Literal "raining")
(Many (Or (Literal "dogs")
(Literal "cats")))
> interpret "rainingdogscats1" ex
(True,"1")
> interpret "abc" ex
(False,"abc")
20. Таким образом
• Шаблон Интерпретатор определяет грамматику:
data Expr = Literal String |
And Expr Expr |
Or Expr Expr |
Many Expr
And (Literal "raining")
(Many (Or (Literal "dogs")
(Literal "cats")))
• … представляет предложения на этом языке:
• … и интерпретирует их:
interpret = …
21. Console I/O
Можно ли применить шаблон
Интерпретатор для языка консольного
ввода-вывода?
22. Console I/O
Можно ли применить шаблон
Интерпретатор для языка консольного
ввода-вывода?
Можно
35. Free f является монадой!
Утверждение: если f является функтором, то
Free f является монадой.
data Free f a = Return a |
Free (f (Free f a))
36. 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)
43. Пишем интерпретатор
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)
46. Тест
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)
50. Таким образом
• Шаблон Интепретатор определяет грамматику:
data Command a = PrintLine String a |
ReadLine (String → a)
deriving Functor
do s ← readLine
printLine s
• … представляет предложения на этом языке:
• … и интерпретирует их:
runConsole = …
testConsole = …
51. Пример 2: Key-Value Store
data KVSCommand a =
Put String String a |
Get String (String → a) |
Delete String a
deriving Functor
type KVS = Free KVSCommand
52. Пример 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 ()))
53. Пример 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)
55. Интерпретатор 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
56. Интерпретатор 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)
57. Эффективный интерпретатор
do put "key1" "value1"
put "key2" "value2"
put "key3" "value3"
"key1" ,"value1"
"key2" ,"value2"
"key3" ,"value3"
58. Эффективный интерпретатор
do put "key1" "value1"
put "key2" "value2"
put "key3" "value3"
"key1" ,"value1"
"key2" ,"value2"
"key3" ,"value3"
["key1" ,"value1",
"key2" ,"value2",
"key3" ,"value3"]
59. Пример 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
60. 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])
61. Хотим достать твиты с фото
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))
62. Хотим достать твиты с фото
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"
63. Хотим достать твиты с фото
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 запроса!
64. Решение
data TwitterCommand a =
GetTweets String Int ([Tweet] → a) |
GetUser String (User → a)
deriving Functor
type Twitter = Free TwitterCommand
65. Решение
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)
66. Кэширующий интерпретатор
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)
Кэш фото
67. Хотим достать твиты с фото
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 запрос