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