YAMAMOTO Yuji (山本悠滋)
2021-11-07 Haskell Day 2021
Compared to Slack Archive, whose goal is same:
slack-log save:
slack-log generate-html:
Look into the cabal file!
app directory contains slack-log
command’s source code, and there is Main.hs, which contains
the main function
Main.hs from the beginning by the first line
of Slack Web API callsFirst of all, open app/Main.hs:
import UI.Butcher.Monadic ({- ... snip ... -})
main :: IO ()
main = do
-- ... snip ...
mainFromCmdParserWithHelpDesc $ \helpDesc -> do
addHelpCommand helpDesc
addCmd "save" $ addCmdImpl saveCmd
addCmd "generate-html" $ do
-- ... snip ...
addCmdImpl $ generateHtmlCmd onlyIndex
addCmd "paginate-json" $ addCmdImpl paginateJsonCmdslack-log saveAdd the subcommand save here
slack-log save command availablesaveCmd is the actually executed IO
actionsaveCmd Function (Overview)Divided into 3 parts:
saveCmd Function (Load Config 1)saveCmd Function (Load Config 2)saveCmd = do
-- ... --
apiConfig <- Slack.mkSlackConfig . slackApiToken =<< failWhenLeft =<< decodeEnv
-- ... --saveCmd Function (Load Config 3)saveCmd Function (Save Users List)saveUsersList function
saveUsersList to see how slack-log calls
Slack’s Web APIsaveUsersList FunctionsaveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
result <- Slack.usersList
-- ... Save users list extracted from the `result` ...Slack.usersList is the function imported from the
slack-web package
usersList though it’s referred to as
Slack.usersList by Haskell’s qualified import
featureReaderT・runReaderT (1)The point here is:
ReaderT automatically passes the argument given by
runReaderT to all functions listed in the do
expressionsReaderT・runReaderT (2)Expressions using ReaderT I referred before are typed as
following:
runReaderT :: ReaderT r IO a
-> r -> IO a
usersList :: ReaderT r IO (Response ListRsp)
saveUsersList :: ReaderT r IO ()runReaderT receives r (in
slack-log, instantiated as SlackConfig), then
transforms a Monad called (somewhat longer named)
“ReaderT r IO” into just IOReaderT・runReaderT (3)Expressions using ReaderT I referred before are typed as
following:
runReaderT :: ReaderT r IO a
-> r -> IO a
usersList :: ReaderT r IO (Response ListRsp)
saveUsersList :: ReaderT r IO ()usersList, all functions to call Slack’s Web API
are typed as a “ReaderT r IO” Monad function
saveUsersList is also typed as a
“ReaderT r IO” function tooMonad function, the caller
needs to be the same type of Monad function tooReaderT?Monads’ features in a
single do syntax by creating a composed Monad
do syntax of “ReaderT r IO”,
both “Reader r” and IO are availableIO would be popular enough. How about
“Reader r”?Reader?“Reader r a” is just an equivalent
(newtype-ed) type of “r -> a”:
do as a
Monad!Reader r” make functions a Monad?
doReader’s
doExample: A function defined NOT using do of “Reader
r”:
someFunc :: Arg -> [Result]
someFunc arg =
let r1 = f1 arg
r2 = f2 arg
r3 = f3 arg
in r1 ++ r2 ++ r3
-- To use it, just apply to an argument👇
someFunc argReader’s doExample: Define a function equivalent to the last slide’s using
do of “Reader r”:
someFunc :: Reader Arg [Result]
someFunc = do
r1 <- f1
r2 <- f2
r3 <- f3
return $ r1 ++ r2 ++ r3
-- To use it, apply runReader to it with an argument👇
runReader someFunc argarg only once
to use runReader!Reader does in dorunReader to every
function listed in the do
Reader by manually passing argsReader provides is sometimes
important: e.g. Creating a embedded DSLusersList function👆 is virtually same with 👇
ReaderT?SlackConfig to every function
calling Slack’s Web API such as usersList
ReaderT SlackConfig IO where possibleReaderT is used for the “ReaderT Pattern”,
the most popular use case of Monad Transformers
usersList) are typed to use with the “ReaderT
Pattern”
ReaderT Pattern”.
Sorry!ReaderT?It’s easy enough to convert between
“ReaderT SlackConfig IO a” and
“SlackConfig -> IO a”
Libraries not directly related to the “ReaderT
Pattern” should NOT force users to use ReaderT so that
newbies don’t get confused
Actual type of usersList:
Its simplified version:
ReaderT (Recap 1)Lines to save users list (repeated):
saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
result <- Slack.usersList
-- ... Save users list extracted from the `result` ...ReaderT (Recap 2)With runReaderT and ReaderT,
becomes:
ReaderT (Recap 3)With runReaderT and ReaderT,
saveUsersList :: Slack.SlackConfig -> IO ()
saveUsersList apiConfig = do
result <- Slack.usersList apiConfig
-- ... Save users list extracted from the `result` ...becomes:
saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
result <- Slack.usersList
-- ... Save users list extracted from the `result` ...rio package?
ReaderT Pattern” more easily!path package?
butcher package, it easily parses command
line argumentsslack-web package,
learn how to use the ReaderT Monad Transformer!
(`runReaderT` apiConfig) someApiFunc without understanding
ReaderTSpace, Right Arrow or swipe left to move to next slide, click help below for more details