Introduction to Slack-log

YAMAMOTO Yuji (山本悠滋)

2021-11-07 Haskell Day 2021

Nice to meet you! 👋😄

📝Topics

🤖What’s slack-log?

A command line app which automatically makes this👇:

🤖What’s slack-log? (cont.)

Why?

😤Pros of slack-log

Compared to Slack Archive, whose goal is same:

😕Cons of slack-log

✍️Usage

🗼Implementation Overview

Look into the cabal file!

⚙️Implementation Details: Main.hs

First 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

⚙️Implementation Details: Main.hs (cont.)

⚙️Implementation Details: slack-log save

Add the subcommand save here

addCmd "save" $ addCmdImpl saveCmd

⚙️saveCmd Function (Overview)

Divided into 3 parts:

  1. Load config files
  2. Save messages
    • It calls Slack’s Web API here!
  3. Convert JSONs into HTMLs

⚙️ saveCmd Function (Load Config 1)

saveCmd = do
  config <- Yaml.decodeFileThrow "slack-log.yaml"
  -- ... --

⚙️ saveCmd Function (Load Config 2)

saveCmd = do
  -- ... --
  apiConfig <- Slack.mkSlackConfig . slackApiToken =<< failWhenLeft =<< decodeEnv
  -- ... --

⚙️ saveCmd Function (Load Config 3)

ws <- loadWorkspaceInfo config "json"

⚙️ saveCmd Function (Save Users List)

(`runReaderT` apiConfig) $ do
  saveUsersList
  -- ... Other functions dependent on apiConfig ...

⚙️ saveUsersList Function

saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
  result <- Slack.usersList
  -- ... Save users list extracted from the `result` ...

📝ReaderTrunReaderT (1)

The point here is:

📝ReaderTrunReaderT (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 ()

📝ReaderTrunReaderT (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 ()

📝What’s ReaderT?

📝Then, what’s Reader?

Reader r a” is just an equivalent (newtype-ed) type of “r -> a”:

📝Define a function WITHOUT Reader’s do

Example: 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 arg

📝Define a function WITH 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

📝What Reader does in do

👀Revisit the usersList function

usersList :: ReaderT SlackConfig IO (Response ListRsp)

👆 is virtually same with 👇

usersList :: SlackConfig -> IO (Response ListRsp)

🤔Why does slack-web use ReaderT?

😕IMO: Should slack-web really use ReaderT?

⚙️Saving users list with ReaderT (Recap 1)

Lines to save users list (repeated):

(`runReaderT` apiConfig) $ do
  saveUsersList
  -- ... Other functions dependent on apiConfig ...
saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
  result <- Slack.usersList
  -- ... Save users list extracted from the `result` ...

⚙️Saving users list with ReaderT (Recap 2)

With runReaderT and ReaderT,

do
  saveUsersList apiConfig
  -- ... Other functions dependent on apiConfig ...

becomes:

(`runReaderT` apiConfig) $ do
  saveUsersList
  -- ... Other functions dependent on apiConfig ...

⚙️Saving users list with 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` ...

Future Steps

Conclusion