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 paginateJsonCmd
slack-log save
Add 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 IO
ReaderT
・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
?Monad
s’ 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?
do
Reader
’s
do
Example: A function defined NOT using do
of “Reader
r”:
Reader
’s do
Example: 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 arg
arg
only once
to use runReader
!Reader
does in do
runReader
to every
function listed in the do
Reader
by manually passing arg
sReader
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):
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:
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
ReaderT