Parsec with ChatGPT
Table of Contents
parsec
Parsec 入門 good
parsec で極める文章編集 good
意味がやっと分った。
ParsecT s u m a s : stream type (String, ByteString, Text のどれか) u : user state type (ユーザが使用できる状態の型、状態を使わないときは () を指定する) m : underlying monad (StateT 複合モナドの内部のモナド、型指定に Parsec s u a を使うときは Identity モナドに指定されている) a : return type (return 関数で戻されるデータの型)
gen by ChatGPT
haskell parsecで、16進数8文字の連続を検出するプログラムって指示。 こんなのを説明付きで提示してきた。8文字の検出関数を作成します。それを 使用する関数を用意します。最後に、mainに組み込みます。
module Main where import Text.Parsec import Text.Parsec.String (Parser) import Text.Parsec (parse) hexParser :: Parser String hexParser = count 8 hexDigit detectHex64 :: String -> Either ParseError String detectHex64 input = parse hexParser "" input main :: IO () main = do let input = "12345678Hello" case detectHex64 input of Left err -> putStrLn $ "Error: " ++ show err Right res -> putStrLn $ "Detected hex string: " ++ res
実行例。
λ> :main Detected hex string: 12345678 λ> :main Error: (line 1, column 5): unexpected "X" expecting hexadecimal digit
5文字目にXを追加したら、それを検出してくれた。
sed ?
マッチした文字列を、別な文字列に置き換える方法をコミュニティーの方に教 えて頂いた。自分では、調べきれなかっただろう。
import Control.Applicative import Text.Parsec.Char import Control.Monad hexParser :: Parser String hexParser = "SHA256" <$ (replicateM_ 8 hexDigit)
これだけで了解してしまっては、飢えている人に魚を恵んでもらうの構図なの で、漁法を探索してみる。
λ> :i replicateM_ replicateM_ :: Applicative m => Int -> m a -> m () -- Defined in ‘Control.Monad’ λ> :i ((<$) type Functor :: (* -> *) -> Constraint class Functor f where ... (<$) :: a -> f b -> f a -- Defined in ‘GHC.Base’ ^ infixl 4 <$
この、<$ の用例は、RWHの409ページにさりげなく出てた。右側の解析器が成 功したら、左側の値を使います、ですって。見逃してしまうよな。もう忘れる 事は無いぞ。
また、全体にマッチさせるには、上記だけでは難しいので、下記の物を勧めら れた。
型
16進数の連続検出に失敗したら、普通に1文字を検出すればいいんでないかい。 そんな考えで色々やったけど、型の壁に阻まれてしまった。基本に還って型確 認。刑事がおこなう行確です。
型宣言を外して、自由に うたってもらう。自供させよって訳。
λ> :t hexParser hexParser :: Parser String λ> :t hexParser hexParser :: ParsecT String u Data.Functor.Identity.Identity [Char]
辻褄合わせで、こんなのにした。本当は anyChar にしたかったんだけどね。
hexParser :: Parser String hexParser = try (count 4 hexDigit) <|> string "BAD"
で、実験すると、
λ> parse hexParser "" "12x3456" Left (line 1, column 3): unexpected "x" expecting hexadecimal digit
tryのブロックの更に奥で違反してると言われた。根本的に駄目な方法だな。
ask ChatGPT
ニュートン誌の特集に取り上げられていた。コード作成の正解率は50%ぐらい と低い。ただ、それでもアイデアを貰ってくるには十分だろう。検証は簡単だ から、余り気にする事は無いだろう。
そんな訳なんで、手詰りなってるのを聞いてみる事にする。 人間が使う言語は、自然言語、それに対してコンピュータが扱う言語の事を、 形式言語と言うそうだ。
形式言語だと、あいまいさが無い。だから、形式言語を与えて、それを haskell語に変換させるのが良いだろう。勿論、お願いは自然言語を使うけど ね。形式言語は、sedにしとく。perl,ruby,pythonとかの祖だからね。
ちょっと、sedで実験。
[sakae@deb ~]$ echo 'python is easy, so I like python.' | sed -e 's/python/haskell/' haskell is easy, so I like python. [sakae@deb ~]$ echo 'python is easy, so I like python.' | sed -e 's/python/haskell/g' haskell is easy, so I like haskell.
gを付けないと、最初に出会った単語しか置換しない。gを付けると、手当たり 次第に痴漢する。おっと、ポルノみたいだな。
んな事で、下記が依頼文。regexを使わないように、モジュールを指示するの が味噌かな。
sed -e 's/old/new/g' と同等な機能をhaskellのparsecモジュールで実現して
import Text.Parsec patternParser :: Parsec String () (String, String) patternParser = do _ <- char 's' _ <- char '/' old <- many1 (noneOf "/") _ <- char '/' new <- many1 (noneOf "/") _ <- char '/' _ <- many (oneOf "g") return (old, new) parsePattern :: String -> Either ParseError (String, String) parsePattern = parse patternParser "" replacePattern :: String -> String -> String -> String replacePattern old new = concatMap replace where replace [] = [] replace str@(c:cs) | isPrefixOf old str = new ++ replace (drop (length old) str) | otherwise = c : replace cs main :: IO () main = do let input = "This is some old text." patternStr = "s/old/new/g" case parsePattern patternStr of Left err -> putStrLn $ "Parse error: " ++ show err Right (old, new) -> putStrLn $ replacePattern old new input
おまけの説明も出てきた。megaparsecって識者の方も推していたな。
なお、parsecモジュールの他にも、attoparsecやmegaparsecなどのパーサーコンビネータ ライブラリも利用できます。具体的な実装方法は異なるかもしれませんが、基本的なアイデアは 同じです。
コンパイルしてみると、こんな2つのエラーが報告された。奴はGHCを持ってい ないからしょうがない。エィヤーとでっちあげて提示するからね。
app/Main.hs:20:36: error: • Couldn't match type ‘[Char]’ with ‘Char’ Expected: Char -> [Char] Actual: [Char] -> [Char] : app/Main.hs:24:9: error: Variable not in scope: isPrefixOf :: String -> [Char] -> Bool :
ざっと見、無理してParsecを使ってみました感がするぞ。sedのコマンドをパー スするのに無理して使っている。肝心な部分は、replacePatternだ。
isPrefixOfは、import Data.List(isPrefixOf) で、導入。それから、 replacePattern内のconcatMapは不要なんで削除。main内の入力文字列と置換 対象を変更してから実行。
λ> :main Haskell is easy, so I like Haskell.
労力の削減になるな。折角なんで、ありがちなエラーを発生させてみる。
let input = "python is easy, so I like python." patternStr = "s/python/Haskell" -- need / λ> :main Parse error: (line 1, column 17): unexpected end of input expecting "/"
パターンの最後のgは飾りの扱い。手当たり次第の置換器になってる。
念の為、hlintの意見も聞いてみた。
app/Main.hs:24:9-26: Suggestion: Use infix Found: isPrefixOf old str Perhaps: old `isPrefixOf` str
ふーん、kaskell業界は、これが主流なのか。
コンパイルエラーとか、こういう習慣を奴にフィードバックすべきだろうか? 日本人なら、一宿一飯の義理があるから、返礼すべきだろね。 それが巡り巡って皆の幸福につながる? より賢くなるけど、ユーザーの機密 情報が漏れる可能性がある。セキュリティー に注意って言われる訳だ。前任では、gmailが無料な事を思いだせ、だな。
多大なフィードバックが欲しい。そうじゃ なければ、無料で公開するはずはない。販促目的だけなら、機能の制限とか、 利用期間の限定だけでいいからね。 奴がどんな返答するか、聞いてみればいいのか。
はい、フィードバックをお願いします。コンパイルエラーの内容と、Haskellの良き習慣について 教えてください。修正や改善の方法についてアドバイスを提供します。
どうしてもマウントを取りたいのだな。人間みたいな奴だ。こういう会話は楽 しい。一人でプログラミングするより、ペアプロみたいで知見が広がる。 ニュートン誌の第二特集は、70才以上の取扱説明書だった。それによると、孤 独は心身共に非常にダメージが有るそうだ。
ちょっと生意気な孫と、ペアプロを楽しむ、おじいちゃんの構図だな。AIの一 人者である松尾先生も認めているみたいだしね。
find <count 64 hexDigit>
いよいよ本命を問い合わせ。
文字列の中から、連続する16進数文字64個を検出して、それを"sha"に置き換える(それ以外は、そのまま)コードを作れ。 haskellのparsecモジュールを使用する事。
最初にこんなコードを提示してきた。
import Text.Parsec import Text.Parsec.String (Parser) -- パーサー: 16進数文字の64個の連続を検出し、それを"sha"に置き換える parser :: Parser String parser = do hexChars <- count 64 hexDigit return $ if all isHexDigit hexChars then "sha" else hexChars -- 文字列をパースして結果を取得 parseString :: String -> Either ParseError String parseString = parse parser "" -- テスト用例 testString :: String testString = "This is a sample string with 64 hex characters: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" main :: IO () main = case parseString testString of Left err -> print err Right result -> putStrLn result
Data.Charを追加するのは、お約束? それにしても、エラーってるぞ。
λ> :main (line 1, column 1): unexpected "T" expecting hexadecimal digit
このエラーを貼り付けて再度問い合わせ。おっしゃる通りって言ってきて、修 正コードが提示された。(この間、何度かのやりとり有り。オイラーはひたす ら、コピペ実行委員でしたよ。)
import Text.Parsec import Text.Parsec.String (Parser) parser :: Parser String parser = do content <- many (try hexString <|> nonHexString) return (concat content) where hexString = do hexChars <- count 64 hexDigit return "sha" nonHexString = do nonHexChars <- many1 (noneOf ("0123456789ABCDEFabcdef" :: String)) return nonHexChars parseString :: String -> Either ParseError String parseString = parse (parser <* eof) "" testString :: String testString = "This is a sample string with 64 hex characters: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" main :: IO () main = case parseString testString of Left err -> print err Right result -> putStrLn result
こんなエラーになった。
λ> :main (line 1, column 10): unexpected " " expecting hexadecimal digit
そして最後通告っぽい、泣き言が、、、
もしもまだエラーが発生する場合は、使用しているParsecライブラリのバージョンによって 動作が異なる可能性があります。Parsecのバージョンによっては、上記のコードが正常に動作 しないことがあります。その場合は、別のパーサーコンビネータライブラリ (例:Megaparsec)を検討することをお勧めします。
上記のエラーを分析すると、"This is a " の a を発見して、その後に16進が 続くと期待したんだな。が、それを裏切られたので、文句を言ったんだな。
ちょっと全体のコードを眺めると、
parseString = parse (parser <* eof) ""
が気になった。これって、eof が成功するまで、左項(すなわち、parser)を実 行しろって事だな。C言語にある、 do { … } while の事だな。
gold data
最初から難しいデータの解析では、萎えるので、ゴールドデータを与えてみる。 パーサーも規模を縮小しておく。 's/hexDigit x 6/__/' 名前もptって具合に unixerっぽく短縮名です。
gold = "QRZ123456QTHabcdef" tc = parseTest (pt <* eof) gold λ> tc "QRZ__QTH__"
面倒なのでコードとghciとのセッション(頭がλで始まる)を併記します。 parseTestはparseの簡易版。ちょいと実験に使うには、うってつけ。
なお、 Qで始まる3文字は、無線業界の隠語です。気にしないで下さい。
s/QRZ/QRA/ に変更すると、(往年のパソコン通信みただな)
λ> tc parse error at (line 1, column 10): unexpected "Q" expecting hexadecimal digit
A12345 を検出、続いて6は16進数だな。期待して次の文字にとりかかると、Q が出てきた。話が番うじゃん。
gold = "543210QRZ123456QTHabcdefQRO" λ> tc "__QRZ__QTH__QRO"
QRO(出力増大)しても、クリーンなデータである限り、問題を撒き散らす事は 無さそうです。
tryの正体
問題は、count 6 hexDigit だな。これを tryで囲って、臭い物に蓋をしよう にも、効き目がない。tryは使えない子?
まずは、countだ。Conbinamtor.hsに存在した。
count :: (Stream s m t) => Int -> ParsecT s u m a -> ParsecT s u m [a] {-# INLINABLE count #-} count n p | n <= 0 = return [] | otherwise = sequence (replicate n p)
定義は簡単だな。sequenceは良く知らない。何だかモナドを返すでいいのかな。
λ> :t sequence sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
これじゃ、ブルトーザーみたいに、16進数をパースしちゃう。これが問題の根 源。これを何とか解消しない限りだめだ。
Parsec コンビネータ count 詳しい説明を発見。
他に注目できる事がないか、tryを参照しとく。
λ> :i try try :: ParsecT s u m a -> ParsecT s u m a -- Defined in ‘Text.Parsec.Prim’
try :: ParsecT s u m a -> ParsecT s u m a try p = ParsecT $ \s cok _ eok eerr -> unParser p s cok eerr eok eerr
newtype ParsecT s u m a = ParsecT {unParser :: forall b . State s u -> (a -> State s u -> ParseError -> m b) -- consumed ok -> (ParseError -> m b) -- consumed err -> (a -> State s u -> ParseError -> m b) -- empty ok -> (ParseError -> m b) -- empty err -> m b }
チンプンカンプンであります。