slack-log の紹介

YAMAMOTO Yuji (山本悠滋)

2021-11-07 Haskell Day 2021

はじめまして! 👋😄

📝今日話すこと

🤖slack-logとは?

これ👇を自動で作るコマンドです!

🤖slack-logとは?(続き)

なぜ作った?

😤slack-logのいいところ

同じ目的であるSlack Archiveに対して:

😕slack-logのイマイチなところ

✍️使い方

🗼実装概要

cabalファイルを見てみよう

⚙️実装解説: Main.hs

まずはapp/Main.hsを見てみよう:

import UI.Butcher.Monadic ({- ... 省略 ... -})

main :: IO ()
main = do
  -- ... 省略 ...
  mainFromCmdParserWithHelpDesc $ \helpDesc -> do
    addHelpCommand helpDesc
    addCmd "save" $ addCmdImpl saveCmd
    addCmd "generate-html" $ do
      -- ... 省略 ...
      addCmdImpl $ generateHtmlCmd onlyIndex
    addCmd "paginate-json" $ addCmdImpl paginateJsonCmd

⚙️実装解説: Main.hs (続き)

⚙️実装解説: slack-log save

この行で、サブコマンドsaveを追加

addCmd "save" $ addCmdImpl saveCmd

⚙️実装解説: saveCmd関数(概要)

3つのパート:

  1. 設定の読み込み
  2. 保存
    • SlackのWeb APIを呼ぶのはここ!
  3. HTMLへの変換

⚙️ saveCmd関数(設定の読み込み その1)

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

⚙️ saveCmd関数(設定の読み込み その2)

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

⚙️ saveCmd関数(設定の読み込み その3)

ws <- loadWorkspaceInfo config "json"

⚙️ saveCmd関数(ユーザー一覧の保存)

(`runReaderT` apiConfig) $ do
  saveUsersList
  -- ... 他にも apiConfig に依存した処理

⚙️ saveUsersList関数

saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
  result <- Slack.usersList
  -- ... result からユーザーの一覧を取り出して保存

📝ReaderT型・runReaderT関数 (1)

先に結論:

📝ReaderT型・runReaderT関数 (2)

今回紹介した箇所では次のような型に:

runReaderT    :: ReaderT r IO a
                   -> r -> IO a
usersList     :: ReaderT r IO (Response ListRsp)
saveUsersList :: ReaderT r IO ()

📝ReaderT型・runReaderT関数 (3)

今回紹介した箇所では次のような型に:

runReaderT    :: ReaderT r IO a
                   -> r -> IO a
usersList     :: ReaderT r IO (Response ListRsp)
saveUsersList :: ReaderT r IO ()

📝ReaderTとは?

📝ではReaderとは?

実は「Reader r a」は「r -> a」と等価な(newtypeした)もの:

📝Readerdo(を使わなかった場合)

例: 「Reader r」のdoを使う前のただの関数

someFunc :: Arg -> [Result]
someFunc arg =
  let r1 = f1 arg
      r2 = f2 arg
      r3 = f3 arg
   in r1 ++ r2 ++ r3

-- 使う時は単純にこう👇
someFunc arg

📝Readerdo(を使った場合)

例: 「Reader r」のdoを使った関数

someFunc :: Reader Arg [Result]
someFunc = do
  r1 <- f1
  r2 <- f2
  r3 <- f3
  return $ r1 ++ r2 ++ r3

-- 使う時は runReader を使ってこう👇
runReader someFunc arg

📝Readerdoでしていること

👀改めてusersList関数

usersList :: ReaderT SlackConfig IO (Response ListRsp)

👆は結局のところ👇と実質同じ

usersList :: SlackConfig -> IO (Response ListRsp)

🤔なぜReaderTを使っているのか

😕私見: 本当にReaderTを使うべきか?

⚙️ ユーザー一覧の保存 まとめ

「ユーザー一覧の保存」をしている箇所(再掲)

(`runReaderT` apiConfig) $ do
  saveUsersList
  -- ... ほかにも apiConfig に依存した処理
saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
  result <- Slack.usersList
  -- ... result からユーザーの一覧を取り出して保存

⚙️ ユーザー一覧の保存 まとめ(続き1)

runReaderTReaderTによって

do
  saveUsersList apiConfig
  -- ... ほかにも apiConfig に依存した処理

が、

(`runReaderT` apiConfig) $ do
  saveUsersList
  -- ... ほかにも apiConfig に依存した処理

になって

⚙️ ユーザー一覧の保存 まとめ(続き2)

runReaderTReaderTによって

saveUsersList :: Slack.SlackConfig -> IO ()
saveUsersList apiConfig = do
  result <- Slack.usersList apiConfig
  -- ... result からユーザーの一覧を取り出して保存

が、

saveUsersList :: ReaderT Slack.SlackConfig IO ()
saveUsersList = do
  result <- Slack.usersList
  -- ... result からユーザーの一覧を取り出して保存

になる!

今後の展望

まとめ