噂のconduit。今回は標準入力触るスクリプトにしたのでconduit.hsみたいな感じで以下を保存。標準入力への入力を大文字にするだけ。.
の入力で終了する。
module Main (main) where
import Data.Char (ord, chr, toUpper)
import Data.Conduit
import qualified Data.Conduit.Binary as B
import qualified Data.Conduit.Util as U
import qualified Data.ByteString as BS
import Data.Word (Word8)
import System.IO (stdin, stdout)
main :: IO ()
main = runResourceT $ do
B.sourceHandle stdin
$= B.takeWhile (\c -> c /= word8 '.')
$= filterUc
$$ B.sinkHandle stdout
return ()
where
word8 = fromIntegral . ord
filterUc :: Monad m => Conduit BS.ByteString m BS.ByteString
filterUc = U.conduitState
()
(\() str -> return $ U.StateProducing () [ucString str])
(\() -> return [])
where
uc :: Word8 -> Word8
uc = fromIntegral . ord . toUpper . chr . fromIntegral
ucString :: BS.ByteString -> BS.ByteString
ucString = BS.map uc
Source
とSink
を$$
で合体させると処理が走り、何らかの値を出力する。ここではsourceHandle
とsinkHandle
を使ってHandle
ベースのごく自然なSource
とSink
を作って動かしてる。m
は任意のモナドでいいのだけど、IO
扱う時はResourceT
を要求される。Conduit
がフィルタ的なもので、Source
とSink
の間に好きなだけ挟める。今回みたいにm
がIO
をベースとしたモナドなら色々できるはず。
Conduit
を間に挟む場合は*nixの|ほど単純ではなく、型の都合で$=
と=$
と=$=
を使い分けないとならない。とはいえ、Source $=
Conduit == Source、Conduit =$
Sink == Sink、Conduit =$=
Conduit == Conduit ってだけだから、ま、そんな苦じゃないはず(=のつく方にConduit(導管)を置くって覚えればいいだけ)。
一応実行してみると以下の通り。
% runhaskell conduit.hs
abcdefg
ABCDEFG
This is Haskell.
THIS IS HASKELL
自分でconduit定義する時はstateConduit
を使うのが常套手段なのかな。あと、複数個のストリーム扱う時はどうすんだろ。