Регистрация в рамках парадигмы функционального программирования
Я предпочитаю как можно ближе придерживаться функциональной парадигмы, сжимая как можно ближе к чисто функциональному, когда мой мозг справляется с задачей. Я использую 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?