简体   繁体   English

无标记最终效果传播

[英]Tagless-final effect propagation

The tagless-final pattern lets us write pure functional programs which are explicit about the effects they require. 无标记最终模式让我们编写纯函数程序,明确它们所需的效果。

However, scaling this pattern might become challenging. 但是,扩展此模式可能会变得具有挑战性 I'll try to demonstrate this with an example. 我将尝试用一个例子来证明这一点。 Imagine a simple program that reads records from the database and prints them to the console. 想象一个简单的程序,它从数据库中读取记录并将它们打印到控制台。 We will require some custom typeclasses Database and Console , in addition to Monad from cats/scalaz in order to compose them: 我们将需要一些自定义类型类DatabaseConsole ,以及来自cats / scalaz的Monad以构成它们:

def main[F[_]: Monad: Console: Database]: F[Unit] =
  read[F].flatMap(Console[F].print)

def read[F[_]: Functor: Database]: F[List[String]] =
  Database[F].read.map(_.map(recordToString))

The problem starts when I want to add a new a effect to a function in the inner layers. 当我想为内层中的函数添加新效果时,问题就开始了。 For example, I want my read function to log a message if no records were found 例如,如果没有找到记录,我希望我的read功能记录消息

def read[F[_]: Monad: Database: Logger]: F[List[String]] =
  Database[F].read.flatMap {
    case Nil => Logger[F].log("no records found") *> Nil.pure
    case records => records.map(recordToString).pure
  }

But now, I have to add the Logger constraint to all the callers of read up the chain. 但现在,我必须将Logger约束添加到read链的所有调用者。 In this contrived example it's just main , but imagine this is several layers down a complicated real-world application. 在这个人为的例子中,它只是main ,但想象这是一个复杂的现实世界应用程序的几个层次。

We can look at this issue in two ways: 我们可以通过两种方式来看待这个问题:

  1. We can say it's a good thing that were explicit about our effects, and we know exactly which effects are needed by each layer 我们可以说明确我们的效果是一件好事,我们确切地知道每一层需要哪些效果
  2. We can also say that this leaks implementation details - main doesn't care about logging, it's just needs the result of read . 我们也可以说这泄漏了实现细节 - main不关心日志记录,它只需要read的结果。 Also, in real applications you see really long chains of effects in the top layers. 此外,在实际应用中,您会在顶层看到很长的效果链。 It feels like a code-smell, but I can't put my finger on what other approach I can take. 这感觉就像一个代码味道,但我无法指出我能采取的其他方法。

Would love to get your insights on this. 很想得到你对此的见解。

Thanks. 谢谢。

We can also say that this leaks implementation details - main doesn't care about logging, it's just needs the result of read. 我们也可以说这泄漏了实现细节 - 主要不关心日志记录,它只需要读取的结果。 Also, in real applications you see really long chains of effects in the top layers. 此外,在实际应用中,您会在顶层看到很长的效果链。 It feels like a code-smell, but I can't put my finger on what other approach I can take. 这感觉就像一个代码味道,但我无法指出我能采取的其他方法。

I actually believe the contrary is true. 我实际上相信相反的是真的。 One of the key promises of pure FP is equational reasoning as a means of deriving the method implementation from it's signature. 纯FP的一个关键承诺是等式推理,作为从它的签名中导出方法实现的一种手段。 If read needs a logging effect in order to do it's business, then by all means it should be declaratively expressed in the signature. 如果read需要记录效果才能完成业务,那么无论如何都应该在签名中以声明方式表达。 Another advantage of being explicit about your effects is the fact that when they start to accumulate, perhaps we need to rethink what this specific method is doing and split it up into smaller components? 明确你的影响的另一个好处是,当它们开始积累时,或许我们需要重新考虑这个特定方法正在做什么并将其拆分成更小的组件? Or should this effect really be used here? 或者这种效果真的应该在这里使用吗?

It is true that effects stack up, but as @TravisBrown mentioned in the comments, it is usually the highest place in the call stack that has to "suffer the consequence" of actually providing all the implicit evidence for the entire call tree. 确实,效果会叠加,但正如评论中提到的@TravisBrown一样,它通常是调用堆栈中最高的位置,必须“承受”实际上为整个调用树提供所有隐式证据的后果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM