5

私が取り組んでいる Haskell プロジェクトで「citation-resolve」パッケージを使用しようとしていますが、実際のコードで EachT を使用するのに苦労しています。それらがモナド変換子であることはわかりました。それが何を意味するのか理解できたと思いますが、実際にそれらを使用する方法を理解できないようです。私がやろうとしていることを表すおもちゃの例は次のとおりです。

module Main where
import Text.EditDistance
import Text.CSL.Input.Identifier
import Text.CSL.Reference
import Control.Monad.Trans.Class 
import Control.Monad.Trans.Either 

main = do
    putStrLn "Resolving definition"
    let resRef = runEitherT $ resolveEither "doi:10.1145/2500365.2500595"
    case resRef of 
                Left e -> do 
                    putStrLn ("Got error: "++ e)
                Right ref -> do
                    putStrLn ("Added reference to database: "++ (show ref))

ここでresolveEitherは、次のタイプがあります。

resolveEither :: (HasDatabase s,
                  Control.Monad.IO.Class.MonadIO m,
                  mtl-2.1.3.1:Control.Monad.State.Class.MonadState s m)
                   => String -> EitherT String m Reference

runEitherT $ resolveEither "ref"タイプは次のとおりです。

runEitherT $ resolveEither "ref"
   :: (HasDatabase s,
       Control.Monad.IO.Class.MonadIO m,
       mtl-2.1.3.1:Control.Monad.State.Class.MonadState s m)
         => m (Either String Reference)

ただし、これにより次のエラーが発生します。

Main.hs:10:34:
    No instance for (Control.Monad.IO.Class.MonadIO (Either [Char]))
      arising from a use of ‘resolveEither’
    In the first argument of ‘runEitherT’, namely
      ‘(resolveEither "doi:10.1145/2500365.2500595")’
    In the expression:
      runEitherT (resolveEither "doi:10.1145/2500365.2500595")
    In an equation for ‘resRef’:
        resRef = runEitherT (resolveEither "doi:10.1145/2500365.2500595")

解決方法や回避方法がわかりません。

特に、実装ではなく使用の観点からモナドトランスフォーマーを扱うチュートリアルへのポインターを歓迎します。

編集:

dfeuer と Christian の回答に対するコメントを反映するために、main を次のように変更してもエラーが発生します。

main = do
    putStrLn "Resolving definition"
    resRef <- runEitherT (resolveEither "doi:10.1145/2500365.2500595")
    case resRef of 
                Left e -> do 
                    putStrLn ("Got error: "++ e)
                Right ref -> do
                    putStrLn ("Added reference to database: "++ (show ref))

私が今得るエラーは次のとおりです。

No instance for (MonadState s0 IO)
  arising from a use of ‘resolveEither’
In the first argument of ‘runEitherT’, namely
  ‘(resolveEither "doi:10.1145/2500365.2500595")’
In a stmt of a 'do' block:
  resRef <- runEitherT (resolveEither "doi:10.1145/2500365.2500595")
In the expression:
  do { putStrLn "Resolving definition";
       resRef <- runEitherT (resolveEither "doi:10.1145/2500365.2500595");
       case resRef of {
         Left e -> do { ... }
         Right ref -> do { ... } } }

ここでは、コメントよりも優れたコードの書式設定がはるかに簡単であるため、コメントと同様に質問を編集しています。

4

3 に答える 3

1

IO (Either String Reference)わかりましたので、関数から型の値を取得していた元の問題の解決策を見つけたと思いますresolveEither(これは、resolveDef関数が提供する関数に対して行われます)。

したがって、resolveEither型を返します

(HasDatabase s, MonadIO m, MonadState s m) => String -> EitherT String m Reference 

タイプの1つに変換できます

(HasDatabase s, MonadIO m, MonadState s m) => String -> m (Either String Reference)

を使用してrunEitherT . resolveEither。質問をしたときにたどり着いたのはここでした。そこから、ソースを見て、ライブラリReferenceが関数からどのように型を抽出したかを確認してみましたresolveEither。ライブラリは次の関数を使用します。

resolve :: (MonadIO m, MonadState s m, HasDatabase s) => String -> m Reference
resolve = liftM (either (const emptyReference) id) . runEitherT . resolveEither

ただし、いずれかを保持したい、つまり削除したいliftM (either (const emptyReference) id)

しかし、これで最初の場所に戻ることができるので、ソースをもう一度見て、この関数がどのように使用されているかを調べました。ライブラリでは、関数は次の中で使用され、出力型を typeresolveの値から type の値に(MonadIO m, MonadState s m, HasDatabase s) => m Reference変換しIO Referenceます。

resolveDef :: String -> IO Reference
resolveDef url = do
  fn <- getDataFileName "default.db"
  let go = withDatabaseFile fn $ resolve url
  State.evalStateT go (def :: Database)

を返す関数を取得するためresolveに、前のものを に置き換えることができます。runEitherT.resolveEitherIO (Either String Reference)

retEither s = do
    fn <- getDataFileName "default.db"
    let go = withDatabaseFile fn $ ( (runEitherT.resolveEither) s)
    State.evalStateT go (Database Map.empty)

( def は の内部でのみ定義されているため、に置き換えまし(def :: Database)た)(Database Map.empty)citation-resolve

全体的なソリューションは次のようになります。

module Main where
import Text.EditDistance
import Text.CSL.Input.Identifier.Internal  
import Text.CSL.Input.Identifier
import Text.CSL.Reference
import Control.Monad.Trans.Either
import Control.Monad.State as State
import qualified Data.Map.Strict as Map

main = do
    putStrLn "Resolving definition"
    resRef <- retEither "doi:10.1145/2500365.2500595" 
    case resRef of 
                Left e -> putStrLn ("Got error: "++ e)
                Right ref -> putStrLn ("Added reference to database: "++ (show ref))

retEither s = do
    fn <- getDataFileName "default.db"
    let go = withDatabaseFile fn $ ((runEitherT.resolveEither) s)
    State.evalStateT go (Database Map.empty)

元の問題を解決します!

ただし、スタイルに関する指針、またはプロセス全体を簡素化する方法は非常に高く評価されます。

于 2014-09-30T13:07:34.677 に答える
1

mtlクラスベースのアプローチの欠点の 1 つに遭遇しました: 威圧的な型エラーです。transformers通常の に基づくモナド変換子で状況がどのように見えるかを想像すると役立つと思います。これが、一般的なモナド変換子を理解するのにも役立つことを願っています。(ちなみに、あなたはすでにこれのほとんどを理解しているようです。私はそれを綴っているだけです。)

型を与えることは、開始するのに最適な方法です。これがあなたが持っていたものです:

resolveEither :: (HasDatabase s,
                  MonadIO m,
                  MonadState s m)
                   => String -> EitherT String m Reference

制約の中に隠された型 がありますがs、これは少し後であなたを苦しめるために戻ってきました。大まかに言えば、制約は次のことを表してsいます。モナドまたはモナドスタックmIOそのベースにあり、モナドスタックのどこかにレイヤーmがあります。これらのプロパティを満たすStateT s最も単純なモナド スタックは. したがって、次のように書くことができます。mHasDatabase s => StateT s IO

resolveEither' :: HasDatabase s
                  => String -> EitherT String (StateT s IO) Reference
resolveEither' = resolveEither

の型を指定しただけなmので、もはや変数ではありません。クラスの制約を満たしている限り、それを行う必要はありません。

これで、モナド変換子が 2 層あることがより明確になりました。メイン関数はモナドにあるので、たとえば記法を使用して「実行」できるIOtype の値を取得したいと考えています。私はこれを、モナド変換子のレイヤーを外側から内側へと「剥ぎ取る」ことだと考えています (これがモナド変換子を「使用する」ということです)。IO<-do

にはEitherT、機能がありますrunEitherT :: EitherT e m a -> m (Either e a)mが「内側」から「外側」に移動する様子がわかるEitherTでしょうか。私にとって、それは重要な直感的な観察です。同様にStateT、 がありrunStateT :: StateT s m a -> s -> m (a, s)ます。

(ちなみに、どちらもレコード アクセサーとして定義されています。これは慣用的ですが、Haddock では少し奇妙に表示され、「間違った」型シグネチャを使用します。Haddocks の「コンストラクター」セクションを調べるのにしばらく時間がかかりました。EitherT e m a ->署名の前になどを精神的に追加します。)

したがって、これは基本的に解決した一般的な解決策になります。 type の適切な値(sこれを と呼びますs) が必要です。(私は頭の中で型をまっすぐに保っていたと仮定しますが、おそらくそうではありませんでした。最初は忘れていました。)その後、パターン マッチまたは を使用して に到達できます。flip runStateT s . runEitherT $ resolveEither "ref"IO ((Either String Reference), s)flipfstEither

GHCがあなたに与えたエラーを説明してほしいなら、私は喜んでいます. 非公式に言えば、「実行」していない、またはすべてのモナド変換子を取り除いていないと言っていました。より正確には、それは観察でIOあり、 のようなものではありませんでしたStateT s IOrunStateTとを使用するrunEitherTことで、クラスの制約が最終的に満たされるように型を強制または制約します。物事を少し間違えると、これは一種の混乱を招きます。

ああ、解決策を書くための慣用的な方法について: 別のretEither関数がここで慣用的であるかどうかはわかりません。グローバルな状態に干渉しているように見えるからです。ライブラリのイディオムがどのようなものかによります。

また、 を使用するevalStateTと、評価後に状態を暗黙のうちに破棄することになりますが、これは悪い考えかもしれませんし、そうでないかもしれません。ライブラリは、データベース接続を再利用することを期待していますか?

最後に、余分な括弧がいくつかあり、型シグネチャがいくつかありません。hlintそれらをお手伝いします。

于 2014-09-30T19:31:50.377 に答える