Регистрация в рамках парадигмы функционального программирования

Я предпочитаю как можно ближе придерживаться функциональной парадигмы, сжимая как можно ближе к чисто функциональному, когда мой мозг справляется с задачей. Я использую F #, когда это возможно. Обычно я застрял либо с VB.NET, либо с С# (или VBA, когда мне действительно не повезло). Поэтому мои языки позволяют мне отклоняться от функционального подхода.

Исторически я игнорировал ведение журнала и общение с пользователем до тех пор, пока у меня не получится результат - просто подождите, пока пользователь будет ждать. Теперь я пытаюсь внедрить протоколирование и/или обновления баров состояния. Это легко, потому что мои языки позволяют мне писать на стандартный вывод, когда захочу. Но с чисто функциональной точки зрения, как же происходит утечка информации о том, что происходит внутри одной функции во внешнем мире? Является ли регистрация или общение с пользователем во время вычислений просто противоречащим чисто функциональному подходу?

Я уверен, что в Haskell можно использовать Монаду. Как насчет использования других языков?

Спасибо.

Ответы

Ответ 1

Давайте взглянем на монадическое решение Хаскелла. Идея ведения журнала заключается в том, что наши вычисления имеют дополнительный метод, который пишет сообщение где-то "вне". Существует множество способов представления таких вычислений, но одним из наиболее общих является создание монады:

class (Monad m) => MonadWriter w m | m -> w where
    tell   :: w -> m ()

Тип w представляет сообщения, а функция tell - это то, что "отправляет" сообщение в монадическое (полное) вычисление.

Примечания:

  • Haskell MonadWriter на самом деле богаче, он содержит функции, которые позволяют исследовать и изменять w, но пока не оставляйте это в стороне.
  • Часть | m -> w не очень важна для объяснения, это просто означает, что w исправлено для заданного m.

Наиболее часто используемой реализацией является Writer, которая в основном представляет собой только пару. Один элемент из него является результатом вычисления, а другой элемент представляет собой последовательность письменных сообщений. (На самом деле это не действительно последовательность, она более общая - моноид, который определяет операции для объединения нескольких сообщений в один.) Вы можете проверить решение Haskell, посмотрев Модуль Writer. Однако он написан в более общем плане, используя WriterT monad transformer, поэтому, если вы не поклонник монады, его можно будет довольно трудно прочитать. То же самое можно сделать и на других функциональных языках, например, этот пример в Scala.

Но существуют и другие возможные, более ориентированные на побочные эффекты (все еще функциональные) реализации вышеупомянутого типа. Мы можем определить tell, чтобы выпустить сообщения на какой-либо внешний приемник, например, на stdout, в файл и т.д. Например:

{-# LANGUAGE FunctionalDependencies, TypeSynonymInstances, FlexibleInstances #-}

instance MonadWriter String IO where
    tell = putStrLn

Здесь мы говорим, что IO может использоваться как средство ведения журнала, которое записывает String в stdout. (Это просто упрощенный пример, полная реализация, вероятно, будет иметь трансформатор монады, который добавит функциональность tell к любой монаде на основе IO.)

Ответ 2

Я новичок в функциональном программировании, но здесь попытка в Scala:

object FunctionalLogging {

  type Result = Int

  class ResultWithLogging(val log: List[String], val result: Result) {}

  def functionWithLogging(log: List[String], arg: String): ResultWithLogging = {
    def function(arg: String): Result = arg.length

    new ResultWithLogging(log :+ ("Calling function(" + arg +")"), function(arg))
  }

  val result = functionWithLogging(List(), "Hello world!")

  // -- Pure functional code ends here --
  println("Result = " + result.result)
  println("Log = " + result.log)
}

Он функционален в том смысле, что побочных эффектов нет, но очевидно, что журнал является частью аргументов функции и возврата, поэтому он не очень элегантный или практичный.

Мне кажется, что протоколирование является желательным побочным эффектом по определению, поэтому, если вы согласитесь с моим определением, вопрос заключается в том, как изолировать нефункциональный код от функционального кода. На практике я, вероятно, начну с объекта Scala (возможно, слишком похожего на одноэлементный - черта, вероятно, лучше Scala) или с актера, чтобы накапливать сообщения регистрации и делать с ними все, что нужно.

Это более прагматичный взгляд: Вход в Scala

Изменить

В этом вопросе говорится о Haskell Monads и IO: Какие еще способы можно обрабатывать состояния на чисто функциональном языке, кроме Monads?