4

特定の計算生物学のファイル形式用のパーサーを作成しているときに、問題が発生しました。

これが私のコードです:

betaLine = string "BETA " *> p_int <*> p_int  <*> p_int <*> p_int <*> p_direction <*> p_exposure <* eol

p_int = liftA (read :: String -> Int) (many (char ' ') *> many1 digit <* many (char ' '))

p_direction = liftA mkDirection (many (char ' ') *> dir <* many (char ' '))
            where dir = oneStringOf [ "1", "-1" ]

p_exposure = liftA (map mkExposure) (many (char ' ') *> many1 (oneOf "io") <* many (char ' '))

ここで、betaLine の定義をコメント アウトすると、すべてがコンパイルされ、個々のパーサー p_int、p_direction、および p_exposure のテストに成功しました。

しかし、その betaLine 方程式が存在する場合、よく理解できない型エラーが発生します。applicative <*> の私の理解は間違っていますか? 最終的には、これが Int -> Int -> Int -> Int -> Direction -> ExposureList を返すようにしたいので、これを BetaPair のコンストラクターに渡すことができます。

タイプエラーは次のとおりです。

Couldn't match expected type `a5 -> a4 -> a3 -> a2 -> a1 -> a0'
            with actual type `Int'
Expected type: Text.Parsec.Prim.ParsecT
                 s0 u0 m0 (a5 -> a4 -> a3 -> a2 -> a1 -> a0)
  Actual type: Text.Parsec.Prim.ParsecT s0 u0 m0 Int
In the second argument of `(*>)', namely `p_int'
In the first argument of `(<*>)', namely `string "BETA " *> p_int'
4

1 に答える 1

5

tl;dr : この式が必要です:

betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int  <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol

以下の理由をお読みください。


繰り返しますが、これは部分的に優先順位の問題です。あなたの現在の行は何をしますか:

string "BETA " *> p_int <*> p_int ...

... 次のようなパーサーを作成するということです。

(string "BETA " *> p_int) <*> (p_int) ...

ただし、これは主な問題ではありません。実際のところ、パーサーの残りの部分が正しければ、上記の意味的に間違ったパーサーでも正しい結果が得られます。ただし、おっしゃる通り、仕組みについて少し誤解があります<*>。その署名は次のとおりです。

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

ご覧のとおり、関数は最初の引数としてファンクターにラップされた関数を取得する必要があります。これは、2 番目の引数のファンクターにラップされた値を使用して適用されます (したがって、アプリカティブ ファンクター)。関数の最初の最初の引数としてそれを与えると、それは a ではなく aであるため、型はチェックされません。p_intParser IntParser (a -> b)

実際のところ、目標があなたの推論で述べたものであるかどうかを型チェックすることはできません。になりたいbetaLineのですがParser (Int -> Int -> Int -> Int -> Direction -> ExposureList)、それはどのように役立ちますか? 4 つIntの s、 aDirectionおよびを受け取る関数を取得し、ExposureListその関数を a のコンストラクターに渡すと、BetaPair魔法のようにそれから a を構築するBetaPairことになっていますか? 関数は右側に関連付けられることに注意してください。したがって、BetaPairコンストラクターに型がある場合:

Int -> Int -> Int -> Int -> Direction -> ExposureList -> BetaPair

... 次と同じ意味ではありません。

(Int -> Int -> Int -> Int -> Direction -> ExposureList) -> BetaPair

これは実際には次のことを意味します。

Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair)))))

代わりに を にすることができますbetaLineParser BetaPairこれはより理にかなっています。(関数矢印の下)<$>の同義語である演算子を使用すると、コンストラクターをファンクターに持ち上げてから、アプリケーション ファンクター インターフェイスを使用して個々の引数をそれに適用できます。関数には次のタイプがあります。fmapBetaPairParser<$>

(<$>) :: Functor f => (a -> b) -> f a -> f b

この場合、持ち上げる最初の引数はBetaPairコンストラクターであり、型を「関数」の型コンポーネントにa変換し、この特定の署名を生成します。bBetaPair

(<$>) :: (Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair)))))) 
      -> f Int -> f (Int -> (Int -> (Direction -> (ExposureList -> BetaPair))))

ご覧のとおり、<$>ここで will が行うことは、左引数として関数を取り、右引数としてファンクターにラップされた値を取り、ラップされた引数を関数に適用することです。

より簡単な例として、f :: Int -> String次の式がある場合:

f <$> p_int

... は整数を解析し、fその整数を引数として関数を適用し、結果をファンクターでラップするため、上記の式は type になりParser Stringます。<$>この位置の のタイプは次のとおりです。

(<$>) :: (Int -> String) -> Parser Int -> Parser String

したがって、using<$>は最初の引数をコンストラクターに適用します。では、他の議論をどのように処理しますか? さて、これが の<*>出番であり、型シグネチャからそれが何をするかを理解していると思います: その使用を連鎖させると、左側のファンクタにラップされた関数にもう 1 つの引数を連続して適用します。ファンクタを右に。したがって、もう一度簡単な例を示します。関数g :: Int -> Int -> Stringと次の式があるとします。

g <$> p_int <*> p_int

式はのg <$> p_int結果p_intを の最初の引数に適用するgため、その式の型は ですParser (Int -> String)。次に<*>、特定のタイプの次の引数を適用します<*>

(<*>) :: Parser (Int -> String) -> Parser Int -> Parser String

したがって、上記の式全体の型は ですParser String

同様に、あなたの状況では、この場合はBetaPairあなたになることができ、次のパターンが得られます。g

BetaPair <$> one <*> parser <*> per <*> argument <*> to <*> betaPair

前述のように、結果のパーサーは次のようになります。

betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int  <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol
于 2012-05-23T20:14:36.637 に答える