haskell - 使用免费的 monad 进行日志记录

标签 haskell logging functional-programming free-monad

此问题与 this article 有关

这个想法是定义一个 DSL 来操作云中的文件,并定义一个
负责不同方面的口译员的组成,例如
与 REST 接口(interface)和日志记录的通信。

为了使这一点更具体,假设我们有以下数据结构
定义了 DSL 的术语。

data CloudFilesF a
= SaveFile Path Bytes a
| ListFiles Path ([Path] -> a)
deriving Functor

我们定义函数来构建 CloudFiles 程序如下:
saveFile :: Path -> Bytes -> Free CloudFilesF ()
saveFile path bytes = liftF $ SaveFile path bytes ()

listFiles :: Path -> Free CloudFilesF [Path]
listFiles path = liftF $ ListFiles path id

然后这个想法是用另外两个 DSL 来解释这个:
data RestF a = Get Path (Bytes -> a)
         | Put Path Bytes (Bytes -> a)
         deriving Functor

data Level = Debug | Info | Warning | Error deriving Show
data LogF a = Log Level String a deriving Functor

我设法定义了从 CloudFiles DSL 到
具有以下类型的 REST DSL:
interpretCloudWithRest :: CloudFilesF a -> Free RestF a

然后给出一个形式的程序:
sampleCloudFilesProgram :: Free CloudFilesF ()
sampleCloudFilesProgram = do
  saveFile "/myfolder/pepino" "verde"
  saveFile "/myfolder/tomate" "rojo"
  _ <- listFiles "/myfolder"
  return ()

可以使用 REST 调用来解释程序,如下所示:
runSampleCloudProgram =
  interpretRest $ foldFree interpretCloudWithRest sampleCloudFilesProgram

尝试使用以下方式定义 DSL 的解释时出现问题
记录。在我上面提到的文章中,作者定义了一个解释器
类型:
logCloudFilesI :: forall a. CloudFilesF a -> Free LogF ()

我们为 Free LogF a 定义一个解释器有类型:
interpretLog :: Free LogF a -> IO ()

问题是这个解释器不能与foldFree就像我在上面所做的那样。所以问题是如何解释一个程序Free CloudFilesF a使用函数logCloudfilesIinterpretLog上面定义?基本上,我正在寻找构造一个类型的函数:
interpretDSLWithLog :: Free ClouldFilesF a -> IO ()

我可以用 REST DSL 做到这一点,但我不能用 logCloudfilesI 做到这一点.

在这些情况下使用免费 monad 时采取的方法是什么?笔记
问题似乎是对于日志记录案例,没有
我们可以为 ListFiles 中的函数提供有意义的值建立
程序的延续。在 second article
作者使用 Halt , 然而,
这在我的 current implementation 中不起作用.

最佳答案

日志记录是装饰器模式的经典用例。

诀窍是在可以访问日志记录效果和一些基本效果的上下文中解释程序。这种 monad 中的指令要么是记录指令,要么是来自基本仿函数的指令。这是仿函数的副产品,基本上是“Either for functors”。

data (f :+: g) a = L (f a) | R (g a) deriving Functor

我们需要能够将程序从一个基本的自由单子(monad)注入(inject)到一个联积仿函数的自由单子(monad)中。
liftL :: (Functor f, Functor g) => Free f a -> Free (f :+: g) a
liftL = hoistFree L
liftR :: (Functor f, Functor g) => Free g a -> Free (f :+: g) a
liftR = hoistFree R

现在我们有足够的结构来编写日志解释器作为围绕其他解释器的装饰器。 decorateLog将日志指令与来自任意自由单子(monad)的指令交错,将解释委托(delegate)给函数CloudFiles f a -> Free f a .
-- given log :: Level -> String -> Free LogF ()

decorateLog :: Functor f => (CloudFilesF a -> Free f a) -> CloudFilesF a -> Free (LogF :+: f) a
decorateLog interp inst@(SaveFile _ _ _) = do
    liftL $ log Info "Saving"
    x <- liftR $ interp inst
    liftL $ log Info "Saved"
    return x
decorateLog interp inst@(ListFiles _ _) = do
    liftL $ log Info "Listing files"
    x <- liftR $ interp inst
    liftL $ log Info "Listed files"
    return x

所以decorateLog interpretCloudWithRest :: CloudFilesF a -> Free (LogF :+: RestF) a是一个解释器,它输出一个程序,其指令集由来自 LogF 的指令组成。和 RestF .

现在我们需要做的就是写一个解释器(LogF :+: RestF) a -> IO a , 我们将从 interpLogIO :: LogF a -> IO a 构建和 interpRestIO :: RestF a -> IO a .
elim :: (f a -> b) -> (g a -> b) -> (f :+: g) a -> b
elim l r (L x) = l x
elim l r (R y) = r y

interpLogRestIO :: (LogF :+: RestF) a -> IO a
interpLogRestIO = elim interpLogIO interpRestIO

所以foldFree interpLogRestIO :: Free (LogF :+: RestF) a -> IO a将运行 decorateLog interpretCloudWithRest 的输出在 IO单子(monad)。整个编译器写成foldFree interpLogRestIO . foldFree (decorateLog interpretCloudWithRest) :: Free CloudFilesF a -> IO a .

在他的文章中,de Goes 更进一步(哈哈)并使用 prisms 构建了这个副产品基础架构。 .这使得对指令集的抽象变得更简单。

extensible-effects 的 USP库是它为您自动使用仿函数副产品进行所有这些争论。如果您打算追求免费的单子(monad)路线(就我个人而言,我并不像 de Goes 那样迷恋它),那么我建议您使用 extensible-effects而不是滚动你自己的效果系统。

关于haskell - 使用免费的 monad 进行日志记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40105759/

相关文章:

parsing - 解析器组合器的类型

haskell - 在 Haskell 中,使用 ExistentialQuantification 的依赖注入(inject)是一种反模式吗?

logging - 有没有一种干净的方法来记录 Groovy 中方法的结果?

scala - 登录 Akka 时如何缩写 ActorRef 的路径?

python - 如何在python函数式编程中实现嵌套for循环?

haskell - 无法通过 `stack install hsdev` 在 Windows 10 上安装 hsev

list - 反转列表时出现意外结果

java - 一个极端使用日志记录的开源项目

list - 为什么模式与变量不匹配?

scala - 如何使有状态 API 变得纯粹