web-dev-qa-db-ja.com

Haskellにログインするにはどうすればよいですか?

HSloggerを使用してプログラムに関する情報を取得しようとしています。だから私は私の関数に次の行を追加します

import Data.Word
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.Bits
import Data.Int
import Data.ByteString.Parser

import System.Log.Logger
import System.Log.Handler.Syslog


importFile :: FilePath -> IO (Either String (PESFile ))
importFile n = do
     warningM "MyApp.Component2" "Something Bad is about to happen."
     ...

関数はIO内にあるため、これは正常に機能します。ただし、次の関数に同様の行を追加すると、

...
parsePES :: Parser PESFile
parsePES = do
        header <- string "#PES"
        warningM "parsing header"
        ...
        return (PESFile ...)

タイプエラーが発生します:

 Couldn't match expected type `Parser a0'
                with actual type `String -> IO ()'
    In the return type of a call of `warningM'
    In a stmt of a 'do' expression: warningM "parsing header"
    In the expression:
      do { header <- string "#PES";
           warningM "parsing header";
        ...

そして、私は完全に理由を完全に理解しています-parsePESはIOモナドではなく、パーサーモナドにあります。私が理解していないのは、それについて何をすべきかです。スタックできるようにモナド変換器が必要ですか?パーサーモナドとIOモナドを一緒に?どうすればいいですか?

47
nont

まず、簡単な免責事項:「ロギング」は、意味があるかどうかに関係なく、ある種の順次実行を想定しているため、通常、Haskellコードでは意味をなさない。 ログがプログラムの実行方法ログがどの値が計算されたかを区別することを確認してください。厳密な命令型言語ではこれらはほとんど同じですが、Haskellでは異なります。

そうは言っても、すでにシーケンシャルでステートフルな計算のコンテキストで、計算された値に基づいてログを記録したいようです。これは、他のほとんどの言語でのログとほぼ同じように機能します。ただし、そうするためのいくつかの手段をサポートするには、モナドが必要です。使用しているパーサーは HCodecsパッケージから のようです。これは比較的制限されているようで、IOを許可せず、モナド変換子として定義されていません。

正直なところ、私のアドバイスは、異なる解析ライブラリの使用を検討することです。 Parsec はデフォルトの選択の一種である傾向があり、私は attoparsec が特定の目的(これにはあなたがやっていることを含むかもしれません)で人気があると思います。どちらを使用しても、ロギングをはるかに簡単に追加できます。Parsecはモナドトランスフォーマーであるため、IOの上に配置し、必要に応じてliftIOを使用できます。一方、attoparsecは増分処理を中心に設計されているため、処理(実際のパーサー内でのロギングはより厄介かもしれませんが)。他にも選択肢はありますが、推奨するための詳細が十分にわかりません。ほとんどのパーサーコンビネーターベースのライブラリは、かなり似通った設計になっている傾向があるため、コードの移植は簡単だと思います。

本当にあなたが持っているものにこだわりたい場合の最後のオプションは、現在使用している解析ライブラリの実装を見て、独自のIOをロールすることです-その指向バージョン。しかし、それはおそらく理想的ではありません。


また、補足として、実際にログを記録しているのではなく、開発の一部としてプログラムの実行を追跡しているだけの場合、GHCiに組み込まれているデバッガーがより役立つか、古き良き Debug.Traceモジュール によるprintfデバッグ。


編集:わかりました、あなた自身のバリエーションをロールすることを検討するもっともらしい理由があるように聞こえます。ここでおおまかに必要なのは、ParserTモナド変換子です。これがParserの現在の定義です:

_newtype Parser a = Parser { unParser :: S -> Either String (a, S) }
_

タイプSはパーサーの状態です。これはStateT S (Either String) aのハードコーディングされたバージョンです。

_newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
_

...ここで_Either String_はエラーモナドとして扱われています。 ErrorTモナド変換子は同じことをします:

_newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
_

したがって、現在の型がStateT S (ErrorT String Identity)と等しい場合、必要なのはStateT S (ErrorT String IO)になります。

モジュール内のほとんどの関数がParserモナドの内部を混乱させていないように見えるので、型定義を単純に置き換え、適切な型を指定する必要がありますクラスインスタンスを作成し、独自のrunParser関数を記述して、準備完了です。

50
C. A. McCann

免責事項:私は Logger haskellフレームワーク の作成者です。

McCannの回答は非常に詳細ですが、質問がなされた時点でHaskellには汎用のログフレームワークが欠けていたことがわかりません。 HSLoggerは現在標準になっていますが、非常に基本的なロギング機能を提供しますが、低速で拡張性がありません。明確にするために、HSLoggerのいくつかの欠陥を以下に示します。

  1. 遅いです。つまり、メッセージをログに記録するたびに、ログの起源を説明する文字列を(非常に簡単な方法で)解析し、内部でいくつかの実存データ型を使用するため、実行時にパフォーマンスのオーバーヘッドが発生します。
  2. IO以外のモナドへのログインは許可されないため、WriterTまたは他のソリューションを使用して、コードを混乱させないようにする必要があります。
  3. 拡張可能ではありません。独自の優先度レベルを作成したり、カスタム動作を定義したり(スレッド間ロギングなど)、コンパイル時のログフィルタリングを行うことはできません。
  4. 行番号やログが置かれたファイル名などの情報は提供されません。そしてもちろん、そのような情報をサポートするように拡張することは非常に困難です。

そうは言っても、 Logger haskell framework を紹介したいと思います。以下を含む、効率的で拡張可能なロギングを可能にします。

  1. 順次の純粋なコードでのログイン(WriterTモナドの実行と使用)
  2. 高度なメッセージフィルタリング(コンパイル時フィルタリングを含む)
  3. スレッド間ロギング機能
  4. TemplateHaskellインターフェースを提供し、ファイル番号やモジュール名などの詳細をログに記録できます
  5. は非常に簡単に拡張できます-すべての機能は単純なBaseLoggerの拡張機能として作成されますが、実用的な機能はありません。明確にするために-フィルタリング機能は、ロガートランスフォーマーとして20行未満で作成され、独自のトランスフォーマーを定義できます。その方法は ドキュメント で説明されています。
  6. デフォルトでは、すべてのプラットフォームでカラー出力を提供します。

ただし、ライブラリはかなり新しいため、必要な機能が不足している可能性があります。良い情報は、この機能を自分で簡単に作成できること、またはGitHubでリクエストを報告することで機能を改善できることです。

ロガーは私が働いている会社( luna-lang.org )によって内部的に開発されており、私たちが作成しているコンパイラーの内部で使用されています。

25
Wojciech Danilo

恥知らずなプラグイン:私はco-logロギングライブラリの作成者です。ライブラリの使用法と実装の詳細については、次のブログ投稿をご覧ください。

このライブラリの背後にある主なアイデアは、ロギングアクションを単純なHaskell関数として扱うことです。関数はHaskellの第一級市民であり、それらを扱うのは非常に簡単です。

0
Shersh