#logging #haskell #monads
#ведение журнала #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."
...
И это отлично работает, потому что функция находится внутри ввода-вывода.
Однако, когда я добавляю аналогичную строку в следующую функцию:
...
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 находится в монаде синтаксического анализатора, а не в монаде ввода-вывода.
Чего я не понимаю, так это что с этим делать. Нужен ли мне преобразователь монады, чтобы я мог объединить монаду синтаксического анализатора и монаду ввода-вывода вместе? Как мне это сделать?
Комментарии:
1. Haskell намного круче, но также имеет более крутую кривую обучения, чем любой другой язык, который я изучал.
2. Откуда
Parser
берется? Используете ли вы Parsec?3. Я использую Data.ByteString. Анализатор. (обновленный код, чтобы отразить это)
Ответ №1:
Во-первых, краткое предупреждение: «протоколирование» обычно не имеет смысла в общем коде Haskell, потому что оно предполагает какое-то последовательное выполнение, которое может иметь смысл, а может и не иметь. Убедитесь, что вы проводите различие между протоколированием того, как выполняется программа, и протоколированием того, какие значения вычисляются. В строго императивных языках это в основном одно и то же, но в Haskell это не так.
Тем не менее, звучит так, как будто вы хотите вести журнал на основе вычисляемых значений в контексте уже последовательного вычисления с отслеживанием состояния, которое в значительной степени работает так же, как ведение журнала на большинстве других языков. Однако вам нужна монада для поддержки некоторых средств для этого. Похоже, что используемый вами анализатор из пакета HCodecs, который кажется относительно ограниченным, не позволяет IO
и не определен как преобразователь монад.
Честно говоря, мой совет состоял бы в том, чтобы рассмотреть возможность использования другой библиотеки синтаксического анализа. Parsec, как правило, используется по умолчанию, и я думаю, что attoparsec популярен для определенных целей (которые могут включать то, что вы делаете). Любой из них позволил бы вам добавить ведение журнала намного проще: Parsec — это преобразователь монады, поэтому вы можете поместить его поверх IO
, а затем использовать liftIO
по мере необходимости, в то время как attoparsec разработан на основе инкрементной обработки, поэтому вы можете разбивать свои входные данные и регистрировать аспекты обработки (хотя ведение журнала внутри фактического анализатора может быть более неудобным). Есть и другие варианты, но я не знаю достаточно подробностей, чтобы давать рекомендации. Большинство библиотек на основе parser combinator, как правило, имеют довольно схожий дизайн, поэтому я ожидаю, что перенос вашего кода будет простым.
Последним вариантом, если вы действительно хотите придерживаться того, что у вас есть, было бы взглянуть на реализацию библиотеки синтаксического анализа, которую вы используете сейчас, и создать свою собственную, IO
ориентированную на нее версию. Но это, вероятно, не идеально.
Кроме того, в качестве дополнения, если то, что вам действительно нужно, на самом деле не ведет журнал, а просто отслеживает выполнение вашей программы в рамках разработки, вы можете счесть более полезным встроенный в GHCi отладчик или старомодную отладку printf через the Debug.Модуль трассировки.
Редактировать: Хорошо, похоже, у вас есть веские причины рассмотреть возможность создания собственного варианта. То, что вам примерно нужно здесь, — это 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
функцию и быть готовыми к работе.
Комментарии:
1. Спасибо за ваш очень продуманный ответ. Я выбрал анализатор из пакета HCodecs, потому что он поддерживает всевозможные немного необычные числовые форматы, такие как Word24 и Word16, как в версиях с малым, так и с большим порядком окончания. Оказывается, мне это нужно, и я не слишком заинтересован в том, чтобы немного подкручивать вручную. Если бы attoparsec поддерживал их, я бы попробовал вместо этого. В противном случае я, вероятно, создам свой собственный на основе версии HCodecs.
2. @nont: Ах, это, безусловно, имеет смысл. Я недостаточно хорошо разбираюсь в том, какие существуют варианты, чтобы сказать, предлагает ли что-нибудь еще такую же поддержку. Если вы создаете свой собственный, вы, вероятно, захотите начать с изучения monad transformers, я собираюсь включить в свой ответ несколько полезных советов по этому поводу, которые могут помочь вам начать.
3. Я думаю, что ваше дополнение об отладке. Трассировка — это то, к чему я действительно стремлюсь, когда создаю свой модуль. трассировка делает свое дело. Спасибо! Я рад за все ваше объяснение, так как общее ведение журнала тоже было бы полезно, а это не то, что я знаю, как сделать.
4. @нонт: А, ладно. Что ж, информация о monad transformers есть там, если вы этого хотите, но
Debug.Trace
часто бывает достаточно, когда вам просто нужно посмотреть, что происходит в непроизводственном коде. Как я уже сказал, это просто старомодная отладка printf, которую я использовал давным-давно, еще до открытия отладчиков. 🙂5. @nont: Хаха, просто рад, что смог помочь! Вы абсолютно правы, что у Haskell более крутая кривая обучения, но я думаю, что это весело и стоит усилий, поэтому я делаю все, что в моих силах, чтобы облегчить процесс…
Ответ №2:
Отказ от ответственности: Я автор фреймворка для ведения журнала Haskell.
Хотя ответ Макканна очень подробный, он не говорит о том, что в Haskell не хватало платформы ведения журнала общего назначения на момент, когда был задан вопрос. HSLogger теперь является стандартным, но он обеспечивает очень простые функции ведения журнала, будучи медленным и не расширяемым. Для ясности, вот некоторые дефекты HSLogger:
- Это происходит медленно. Под медлительностью я подразумеваю, что каждый раз, когда вы регистрируете сообщение, оно анализирует (очень простым способом) строку, описывающую происхождение журнала, и использует некоторые существующие типы данных под капотом, которые должны приводить к некоторым издержкам производительности во время выполнения.
- Это не позволяет входить в другие монады, кроме ввода-вывода, поэтому вам нужно использовать
WriterT
или другие решения, чтобы не испортить ваш код. - Это не расширяемо — вы не можете создавать свои собственные уровни приоритета, определять настраиваемое поведение (например, ведение журнала между потоками) или фильтрацию журналов во время компиляции.
- Он не предоставляет некоторой информации, такой как номера строк или имена файлов, в которые были помещены журналы. И, конечно, очень сложно расширить его для поддержки такой информации.
При этом я хотел бы представить Logger Haskell framework. Это обеспечивает эффективное и расширяемое ведение журнала, в том числе:
- регистрация в последовательном чистом коде (выполняется так же, как и с помощью
WriterT
monad) - расширенная фильтрация сообщений (включая фильтрацию во время компиляции)
- возможность ведения журнала между потоками
- предоставляет
TemplateHaskell
интерфейс, позволяющий регистрировать дополнительные сведения, такие как номера файлов или имена модулей - очень легко расширяется — все функции создаются как расширения к простому
BaseLogger
, который не может сделать ничего разумного. Для ясности — функциональность фильтрации создается менее чем в 20 строках в виде logger-transformer, и вы можете определить свои собственные преобразования. Как это сделать, описано в документации. - По умолчанию предоставляет цветной вывод на всех платформах.
Но библиотека довольно новая, поэтому в ней может отсутствовать некоторая необходимая функциональность. Хорошая информация заключается в том, что вы можете легко создать эту функциональность самостоятельно или помочь нам улучшить ее, сообщив о проблемах на GitHub.
Регистратор разработан внутри компании, в которой я работаю (luna-lang.org ) и используется внутри создаваемого нами компилятора.
Комментарии:
1. отличные документы! Я думаю, вам нужно поместить пустую строку после «Есть несколько вещей, которых здесь нет:» в
System.Log
, чтобы отформатировать список (и исправить опечатку на «примечание»).2. @samboosalis: Спасибо! Самая последняя документация доступна на github ( github.com/wdanilo/haskell-logger ) и его лучше отформатировать там. Я обновлю документы по hackage, когда выйдет новая версия. Спасибо за ваше внимание и помощь! 🙂 Если хотите, вы всегда можете помочь нам и изменить все, что захотите, в документах и создать запрос на извлечение — это было бы очень полезно в будущем! 🙂
3. не беспокойтесь! В этом случае я просто выполню свое собственное предложение. как новый пользователь Haskell, я думал о добавлении / исправлении документации для пакетов, которые я использую, чтобы вернуть небольшую часть. конечно, я должен убедиться, что понимаю пакет, хотя, я думаю, всегда работает простое добавление примеров 🙂