haskell-jp / beginners #21 at 2022-09-15 22:32:38 +0900

conduit の勉強をしていて詰まってしまったので質問させてください。
words.txt から 「apple」 以外の単語を出力させる意図で、下のようなコードを書いたのですが、
実行してみると 「apple」以外の単語も除外されてしまっているようです。
文字列型の扱いが間違っていそうな気がするのですが、原因がわからず。解決方法を教えていただけますでしょうか。

words.txt の中身は https://github.com/tabatkins/wordle-list/blob/main/words です。

module Main where

import Conduit
import Data.Conduit.Binary as CB
import Data.ByteString (ByteString)
import Data.ByteString.UTF8 (toString)

main :: IO ()
main = do
    res <- runConduitRes $ pipeline "./words.txt"
    Prelude.mapM_ print res

producer :: MonadResource m => FilePath -> ConduitT i ByteString m ()
producer file = sourceFile file .| CB.lines

string :: Monad m => ConduitT ByteString String m ()
string = mapC toString

filtering :: Monad m => ConduitT String o m ()
filtering = dropWhileC ( == "apple")
-- filtering = dropWhileC (const True)

pipeline :: MonadResource m => FilePath -> ConduitT i o m [String]
pipeline file =  producer file .| string .| (filtering >> sinkList)

実行結果
$ stack run | grep "apple"
"apple"
@liveinwood
とりあえず先に正解を書いてしまうと以下のような形になります(stack newから書いてるのでmainの位置だけ違う)

module Lib
    ( someFunc
    ) where

import Conduit
import Data.Conduit.Binary as CB
import Data.ByteString (ByteString)
import Data.ByteString.UTF8 (toString)

someFunc :: IO ()
someFunc = do
    res <- runConduitRes $ pipeline "./words.txt"
    Prelude.mapM_ putStrLn res

producer :: MonadResource m => FilePath -> ConduitT i ByteString m ()
producer file = sourceFile file .| CB.lines

string :: Monad m => ConduitT ByteString String m ()
string = mapC toString

filtering :: Monad m => ConduitT String String m ()
filtering = filterC ( /= "apple")

pipeline :: MonadResource m => FilePath -> ConduitT i o m [String]
pipeline file = producer file .| string .| filtering .| sinkList

Conduitに関してそこまで詳しいというわけでは無いのですが、
HaskellではdropWhileはテストが成功する限り削り続けるということになるので、
この意味だとappleでない単語まで削り続けるという意味になるのと、
フィルタリング関数はパイプラインに流して、
副作用だけではなく結果も無視しないということが必要になるのかなと思います。
@ さん返信ありがとうございます。
dropWhile の意味を勘違いしていました。お恥ずかしい。。

本来の質問からそれるのですが、他に腑に落ちない点がありまして。

runConduit $ yieldMany [1..5] .| dropC 2 .| sinkListは結果が空リストで、
runConduit $ yieldMany [1..5] .| (dropC 2 >> sinkList) だと期待どおり`[3,4,5]` が返ってきます。
一方、`runConduit $ yieldMany [1..5] .| filterC (> 2) .| sinkList` だと`[3,4,5]` が返ってきます。
dropC だと
を使い、filterC は .| を使く理由がよくわかっておりません。:tired_face:
@liveinwood
dropCdropWhileCConsumers に分類されて入力を消費するというside effectを起こすのですが、 filterC などは Transformers に分類されて、ストリームを変換するからですね。
型も違います。
Consumers でfilterみたいなのを探してみたのですが、見つからなかったので変換で書き、実際にこういう処理は変換の方が適切かと思いました。
@ さん、説明ありがとうございます。
consumerとtransformerの違いなんですね。まだ理解に自信が持てないのでもう少しいろいろと試してみようと思います。