コンパイル時にファイルからテキストを読み込む
例えばテキストを埋め込むだけなら、
main = putStrLn $(LitE . StringL <$> runIO (readFile "hoge.txt")) -- hoge.txt の中身は hogefuga と書かれているとする。
とするだけで、
main = putStrLn "hogefuga"
と同じ意味になってくれます。
ファイルが無かったり何らかのエラーが発生したらコンパイルエラーになるだけなので安心です。
型を分解していくとこんな感じ。
readFile "hoge.txt" :: IO String runIO :: IO a -> Q a StringL :: String -> Lit LitE :: Lit -> Exp
runIO で IO モナドから Q モナドに変換してくれるので、あとは Q String から文字列リテラルの Q Exp に変換してるだけです。
で、今回やりたかったのは、IP とポート番号を
127.0.0.1 8080
とか記述したファイルを読んで、(String, PortID) を返すことです。
ということでそんな感じのコードをごにょごにょ書いてみました。
ipPortFile :: FilePath -> Q Exp ipPortFile fp = do [ip, port] <- lines <$> runIO (readFile fp) return $ TupE [LitE $ StringL ip, AppE (ConE $ mkName "PortNumber") (LitE $ IntegerL $ read port)]
読んだ文字列を行単位で展開して、IP の方はさっきと同じようにそのまま文字列リテラルにしてやって、ポート番号は PortNumber コンストラクタと数値リテラルをそれぞれ作って AppE で関数適用の式を作ってやりました。
多分 Template Haskell 使わなければ
ipPortFile :: FilePath -> IO (String, PortID) ipPortFile fp = do [ip, port] <- readFile fp return $ (ip, PortNumber $ fromIntegral $ read port)
こんな感じになります。
で、あとはこれを
main = do handle <- uncurry connectTo $(ipPortFile "ip.txt") -- handle を使っていろいろする。
とかするだけです。接続先を書き換えるたびに hs ファイルを書き換える必要が無くなって幸せになれます。
C++ だと #include "ip.txt" とかするだけで……いや何でもないです。
追記
もっと超簡単に書ける方法があったようです。
ipPortFile :: FilePath -> Q Exp ipPortFile fp = do [ip, port] <- runIO $ lines <$> readFile fp let port' = read port :: Int [| (ip, PortNumber $ fromIntegral port') |]gist:1884179 · GitHub