UNIX Domain Socket を使う (クライアント編)

UNIX Domain Socket に対して echo チックなことをやるアプリケーション。本家 echo と同様、引数をそのまま出力する。複数の引数がある場合には、これらを空白で区切る。
本家 echo と違うのは、ソケットファイルのパスを受け取らなければならないということ。

空白で区切るところだけ凝った。あとは素直な実装。

import Data.List (intersperse)
import IO (hPutStrLn, stderr)
import System.Environment (getArgs)
import Network.Socket (Socket, socket, Family(AF_UNIX),
                       SocketType(Stream), SockAddr(SockAddrUnix),
                       connect, send)

-- 指定パスの Unix Domain Socket を開く。
--
open :: FilePath -> IO Socket
open path = do sock <- socket AF_UNIX Stream 0
               ready sock
  where ready sock = do connect sock (SockAddrUnix path)
                        return sock

-- ソケットにメッセージを送信する。
--
echo :: Socket -> String -> IO ()
echo sock msg = send sock msg >> return ()

-- ソケットに指定の全メッセージを送信する。各メッセージの間には空白文字
-- を挟む。
-- 各メッセージを送信するアクションのリストと、空白文字を送信するアクシ
-- ョンを用意する。空白文字送信アクションをリストの各アクションの間に挟
-- む。このリストのアクションを先頭から逐次実行することで、メッセージ ->
-- 空白 ->メッセージ -> 空白 .. -> メッセージの順にソケットへ書き込む。
--
echoAll :: Socket -> [String] -> IO ()
echoAll sock msgs = sequence_ actions
  where sendActs = map (echo sock) msgs
        sendWS = echo sock " "
        actions = intersperse sendWS sendActs

main = do args <- getArgs
          case args of
            []          -> usage
            (path:msgs) -> doEcho path msgs
  where usage = hPutStrLn stderr "PROG <path> [MSG1, MSG2..]"
        doEcho path msgs = do sock <- open path
                              echoAll sock msgs

IO バリバリの Haskell プログラムは Haskell っぽくないと言われることもある。確かに命令型っぽい見た目にはなる。ただ、Haskell であることの恩恵は十分あると思う。遅延評価によってアクションをお手軽に値として扱えることや、モナドをタグに見立てたアクションの型安全性など、これはもうめちゃくちゃ嬉しいことでないかな。