如何通过猫效应效应误差积累?

发布于 2025-01-19 12:13:12 字数 2351 浏览 4 评论 0原文

我一直在努力解决效果中的错误累积问题,我想知道使用猫效应进行建模的惯用方法是什么。

在不涉及效果的情况下,我会做这样的事情:

import cats._
import cats.data._
import cats.implicits._
import cats.effect._

opaque type Version = String
opaque type Path = String
case class ExampleConfig(version: Version, path: Path)

enum ValidationError:
  case InvalidVersion
  case InvalidPath

// Implementation irrelevant to the example
def validateVersion(version: String): ValidatedNec[ValidationError, Version] = ???
def validatePath(path: String): ValidatedNec[ValidationError, Path] = ???

def validateConfig(version: String, path: String): ValidatedNec[ValidationError, ExampleConfig] =
  (validateVersion(version), validatePath(path)).mapN(ExampleConfig.apply)

但是,如果我们现在想象 validatePath 实际上验证文件系统上是否存在该路径,它就会变成类似 Sync 的效果。这使得这种情况变得更加复杂,因为我们现在拥有:

  • 一个我们不想使用的额外错误通道,但确实需要处理
  • 嵌套类型,这在混合有效验证和常规验证以及存在依赖项时很快就会变得麻烦它们之间,需要某种类型的杂耍
// Implementation irrelevant to the example
def pathExists[F[_]: Sync](path: Path): F[Boolean] = ???

def validatePath2[F[_]: Sync](path: String): F[ValidatedNec[ValidationError, Path]] =
  // Such a dependency between validations leads to major painpoints,
  // as we now have an effect inside a validation, while we would want it the other way around
  validatePath(path)
    .fold(
      e => Sync[F].delay(e.invalid),
      path => Sync[F].ifF(pathExists(path))(path.validNec, ValidationError.InvalidPath.invalidNec)
    )

def validateConfig2[F[_]: Sync](version: String, path: String): F[ValidatedNec[ValidationError, ExampleConfig]] =
  // We now have a distinction between our regular validations, and effectful validations
  val versionValid: ValidatedNec[ValidationError, Version] = validateVersion(version)

  for {
    pathValid: ValidatedNec[ValidationError, Path] <- validatePath2(path)
      // We need to deal with any errors in Syncs error channel,
      // in addition to our validation error domain,
      // as there technically can be additional errors in here that we want to map to validation errors
      .handleError(e => ValidationError.InvalidPath.invalidNec)
  } yield (versionValid, pathValid).mapN(ExampleConfig.apply)

是否有更好的、惯用的、使用猫效应建模错误累积效应的方法?

I've been struggling with error accumulation with effects, and I was wondering what the idiomatic way of modeling this is using Cats Effect.

Without involving effects, I would do something like this:

import cats._
import cats.data._
import cats.implicits._
import cats.effect._

opaque type Version = String
opaque type Path = String
case class ExampleConfig(version: Version, path: Path)

enum ValidationError:
  case InvalidVersion
  case InvalidPath

// Implementation irrelevant to the example
def validateVersion(version: String): ValidatedNec[ValidationError, Version] = ???
def validatePath(path: String): ValidatedNec[ValidationError, Path] = ???

def validateConfig(version: String, path: String): ValidatedNec[ValidationError, ExampleConfig] =
  (validateVersion(version), validatePath(path)).mapN(ExampleConfig.apply)

However, if we now imagine validatePath actually verifies whether the path exists on the filesystem, it becomes an effect like Sync. This makes this case significantly more complex, as we now have:

  • an additional error channel that we don't want to use, but do need to deal with
  • nested types, which quickly become a hassle when mixing effectful and regular validations and when there's dependencies between them, requiring some type juggling
// Implementation irrelevant to the example
def pathExists[F[_]: Sync](path: Path): F[Boolean] = ???

def validatePath2[F[_]: Sync](path: String): F[ValidatedNec[ValidationError, Path]] =
  // Such a dependency between validations leads to major painpoints,
  // as we now have an effect inside a validation, while we would want it the other way around
  validatePath(path)
    .fold(
      e => Sync[F].delay(e.invalid),
      path => Sync[F].ifF(pathExists(path))(path.validNec, ValidationError.InvalidPath.invalidNec)
    )

def validateConfig2[F[_]: Sync](version: String, path: String): F[ValidatedNec[ValidationError, ExampleConfig]] =
  // We now have a distinction between our regular validations, and effectful validations
  val versionValid: ValidatedNec[ValidationError, Version] = validateVersion(version)

  for {
    pathValid: ValidatedNec[ValidationError, Path] <- validatePath2(path)
      // We need to deal with any errors in Syncs error channel,
      // in addition to our validation error domain,
      // as there technically can be additional errors in here that we want to map to validation errors
      .handleError(e => ValidationError.InvalidPath.invalidNec)
  } yield (versionValid, pathValid).mapN(ExampleConfig.apply)

Is there a better, idiomatic, way of modeling error accumulating effect using Cats Effect?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文