如何从功能上处理日志记录副作用

问题描述

如果一条记录没有相邻的记录,我想登录。有没有纯粹的功能性方法可以做到这一点?可以将副作用与数据转换区分开来的吗?

这是我需要做的一个例子:

val records: Seq[Record] = Seq(record1,record2,...)

val accountsMap: Map[Long,Account] = Map(record1.id -> account1,...)

def withAccount(accountsMap: Map[Long,Account])(r: Record): (Record,Option[Account]) = {
  (r,accountsMap.get(r.id))
}

def handleNoAccounts(tuple: (Record,Option[Account]) = {
  val (r,a) = tuple
  if (a.isEmpty) logger.error(s"no account for ${record.id}")
  tuple
}

def toRichAccount(tuple: (Record,a) = tuple
  a.map(acct => RichAccount(r,acct))
}

records
.map(withAccount(accountsMap))
.map(handleNoAccounts) // if no account is found,log
.flatMap(toRichAccount)

因此,我认为这种方法存在多个问题,使其无法达到最佳效果。

元组返回类型很笨拙。我必须在后两个函数中都破坏元组。

日志记录功能必须处理日志记录,然后返回没有任何更改的元组。即使没有进行任何转换,也将其传递给.map感觉很奇怪-也许有更好的方法来获得这种副作用。

是否有功能性的方法可以解决此问题?

解决方法

如果您使用的是Scala 2.13或更高版本,则可以使用tapEach,它使用函数A => Unit在函数的每个元素上施加副作用,然后不变地传递集合:

//you no longer need to return tuple in side-effecting function
def handleNoAccounts(tuple: (Record,Option[Account]): Unit = {
  val (r,a) = tuple
  if (a.isEmpty) logger.error(s"no account for ${record.id}")
}

records
.map(withAccount(accountsMap))
.tapEach(handleNoAccounts) // if no account is found,log
.flatMap(toRichAccount)

如果您使用的是旧版Scala,则可以提供扩展方法(根据Levi's Ramsey的建议进行了更新):

implicit class SeqOps[A](s: Seq[A]) {
  def tapEach(f: A => Unit): Seq[A] = {
      s.foreach(f)
      s
  }
}
,

我可能是错的(我经常是),但是我认为这可以满足所有要求。

records
  .flatMap(r => 
    accountsMap.get(r.id).fold{
      logger.error(s"no account for ${r.id}")
      Option.empty[RichAccount]
    }{a => Some(RichAccount(r,a))})

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...