Logging 如何在';对于';理解力

Logging 如何在';对于';理解力,logging,scala,Logging,Scala,对于理解中的日志跟踪,我使用了如下虚拟赋值: val ll = List(List(1,2),List(1)) for { outer <- ll a = Console.println(outer) // Dummy assignment makes it compile inner <- outer } yield inner val ll=List(List(1,2),List(1)) 为了{ 外部您始终可以定义自己的跟踪功能: d

对于
理解中的
日志跟踪,我使用了如下虚拟赋值:

val ll = List(List(1,2),List(1))            

for {
  outer <- ll 
  a = Console.println(outer)   // Dummy assignment makes it compile
  inner <- outer
} yield inner
val ll=List(List(1,2),List(1))
为了{

外部您始终可以定义自己的
跟踪功能:

def trace[T](x: T) = {
  println(x) // or your favourite logging framework :)
  x
}
然后,用于理解的示例将如下所示:

for { 
  outer <- ll
  inner <- trace(outer)
} yield inner
for { 
  outer <- ll
  inner <- trace("Value: " + outer, outer)
} yield inner
理解的过程如下所示:

for { 
  outer <- ll
  inner <- trace(outer)
} yield inner
for { 
  outer <- ll
  inner <- trace("Value: " + outer, outer)
} yield inner
那么,在您提供的代码中,您唯一需要修改的就是将
tracked
添加到要跟踪的值的末尾。例如:

for { 
  outer <- ll
  inner <- outer traced
} yield inner
case class Inter[A](i: Int => A) 
用于{

外部由于赋值是虚拟的,因此无论值多少,您都可以用
\uu
替换
a

for { 
  outer <- ll  // ; // semi-colon needed on Scala 2.7
  _ = Console.println(outer)   // dummy assignment makes it compile 
  inner <- outer 
} yield inner 
用于{

outerFlaviu的回答激发了我尝试玩隐式游戏。这个想法是想看看如果在线条的右边再加上“trace”,痕迹看起来是否更好:

import Trace._

object Main {  
  def main(args:Array[String])  {
    val listList = List(List(1,2,3), List(3,4))    
    for {
      list <- trace1(listList, "lList is: %s", listList)  // trace() 
      item <- list traced("list is: %s", list)            // implicit         
    } yield item

您的问题的简短答案是单子变压器
WriterT
monad transformer
。下面是详细答案

在下面的解释中,我将给你一个实现你期望目标的工具,但是使用了一种与前面所述完全不同的机制。最后,我将对这些差异的优点提出我的简要意见

首先,什么是理解的a?理解的a(对于我们的目的来说差不多足够了)是一个单子理解,但有一个不同的名字。这恰好是一个共同的主题;例如C#有LINQ

什么是单子? 出于我们的解释目的(这并不完全正确,但目前已经足够正确),monad是实现以下特征的
M
的任何值:

trait Monad[M[_]] {
  def flatMap[A, B](a: M[A], f: A => M[B]): M[B]
  def map[A, B](a: M[A], f: A => B): M[B]
}
也就是说,如果您对某些M有一个Monad实现,那么您可以使用a来理解类型为M[a]的值对a的任何值

适用于此界面且在标准库中的M值示例有
列表
选项
解析器
。当然,您可能一直使用它们来理解。其他示例可能是您自己的数据类型。例如:

for { 
  outer <- ll
  inner <- outer traced
} yield inner
case class Inter[A](i: Int => A) 
…下面是
Inter
Monad
实现:

val InterMonad: Monad[Inter] = new Monad[Inter] {
  def flatMap[A, B](a: Inter[A], f: A => Inter[B]) =
    Inter(n => f(a.i(n)).i(n))
  def map[A, B](a: Inter[A], f: A => B) =
    Inter(n => f(a.i(n)))
}
M有许多更多的值。本质上,您的问题是,我们如何为这些值添加日志支持?

写入程序数据类型
Writer
数据类型只是一对(
scala.Tuple2
)。在这对数据中,我们计算一些值(我们称之为
a
),并将另一个值与之关联(我们称之为
LOG

在计算值时,我们希望将日志值附加到当前计算的日志中。在开始计算任何内容之前,我们希望有一个空日志。我们可以在界面中表示这些操作(
append
empty
):

trait Monoid[A] {
  def append(a1: A, a2: A): A
  def empty: A
}
此接口的所有实现都必须遵循一些规则:

  • 关联性:追加(x,追加(y,z))==追加(追加(x,y,z)
  • 右标识:追加(空,x)=x
  • 左标识:追加(x,空)=x
作为旁注,这些也是
Monad
接口的实现必须遵循的相同规则,但为了避免混淆和保持日志记录的重点,我省略了这些规则

Monoid
接口有许多实现示例,其中一个是List:

def ListMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
  def append(a1: List[A], a2: List[A]) = 
    a1 ::: a2
  def empty =
    Nil
}
为了简单地说明这个
Monoid
接口的多样性,下面是另一个实现示例:

def EndoMonoid[A]: Monoid[A => A] = new Monoid[A => A] {
  def append(a1: A => A, a2: A => A) =
    a1 compose a2
  def empty =
    a => a
}
val ListWriterMonad: Monad[ListWriter] = new Monad[ListWriter] {
  def flatMap[A, B](a: ListWriter[A], f: A => ListWriter[B]) = {
    val ListWriter(log, b) = f(a.value)
    ListWriter(a.log ::: log /* Monoid.append */, b)
  }
  def map[A, B](a: ListWriter[A], f: A => B) = 
    ListWriter(a.log, f(a.value))
} 
我知道这些概括可能会变得有点难以记住,所以我现在要做的是,专门让
编写器
使用
列表
中的
字符串
值作为日志。听起来很合理。不过,有几点需要注意:

  • 实际上,我们不会使用
    List
    ,因为它的
    append
    的算法复杂度不理想。相反,我们可能会使用基于手指树的序列或其他在结束操作时插入速度更快的序列
  • List[String]
    只是
    Monoid
    实现的一个例子。请记住,还有大量其他可能的实现,其中许多都不是集合类型。请记住,我们只需要任何
    Monoid
    来附加日志值
  • 这是我们的新数据类型,专门用于
    Writer

    case class ListWriter[A](log: List[String], value: A)
    
    这到底有什么意思?它是一个monad!重要的是,它的
    monad
    实现为我们跟踪日志记录,这对我们的目标很重要。让我们编写实现:

    def EndoMonoid[A]: Monoid[A => A] = new Monoid[A => A] {
      def append(a1: A => A, a2: A => A) =
        a1 compose a2
      def empty =
        a => a
    }
    
    val ListWriterMonad: Monad[ListWriter] = new Monad[ListWriter] {
      def flatMap[A, B](a: ListWriter[A], f: A => ListWriter[B]) = {
        val ListWriter(log, b) = f(a.value)
        ListWriter(a.log ::: log /* Monoid.append */, b)
      }
      def map[A, B](a: ListWriter[A], f: A => B) = 
        ListWriter(a.log, f(a.value))
    } 
    
    请注意,在
    flatMap
    实现中附加了日志值。接下来,我们需要一些用于附加日志值的帮助函数:

    def log[A](log: String, a: A): ListWriter[A] =
      ListWriter(List(log), a)
    
    def nolog[A](a: A): ListWriter[A] =
      ListWriter(Nil /* Monoid.empty */, a)
    

    …现在让我们看一下它的实际操作。下面的代码对于理解来说是非常容易理解的。但是,链接操作并不是从
    开始的
    Scala 2.13
    ,而是包含在标准库中,可以在需要打印某些int的任何地方以最小的干扰使用E管道的中间状态:

    import util.chaining._
    
    // val lists = List(List(1, 2), List(1))
    for {
      outer <- lists
      inner <- outer.tap(println)
    } yield inner
    // List(2, 4, 6)
    // List(4, 8, 12)
    // ls: List[Int] = List(4, 8, 12)
    
    import util.chaining_
    //val lists=列表(列表(1,2),列表(1))
    为了{
    
    外部啊,我不知道你能做到。我想上面的
    外部后面需要一个分号。这是个好主意,thx。你让我考虑了一个可能的改进——如果trace()就好了不会介入主要逻辑的阅读。例如,如果“trace”一词位于“outer”的右侧,我发现第一部分很清楚,非常有用,但你在“WriterT Monad Transformer”中失去了我部分,我想是因为它没有像前面那样的示例用法。您是否愿意添加一个示例来显示ListWriterT/ListWriterMonad的用法?
    
    // The WriterT monad transformer
    case class ListWriterT[M[_], A](w: M[ListWriter[A]])
    
    def ListWriterTMonad[M[_]](m: Monad[M]): 
          Monad[({type λ[α]=ListWriterT[M, α]})#λ] =
      new Monad[({type λ[α]=ListWriterT[M, α]})#λ] {
        def flatMap[A, B](a: ListWriterT[M, A], f: A => ListWriterT[M, B]) =
          ListWriterT(
            m flatMap (a.w, (p: ListWriter[A]) =>
                p match { case ListWriter(log1, aa) => 
            m map     (f(aa).w, (q: ListWriter[B]) =>
                q match { case ListWriter(log2, bb) =>
            ListWriter(log1 ::: log2, bb)})
          }))
        def map[A, B](a: ListWriterT[M, A], f: A => B) = 
          ListWriterT(
            m map (a.w, (p: ListWriter[A]) =>
                p match { case ListWriter(log, aa) => 
            ListWriter(log, f(aa))
          }))
      }
    
    import util.chaining._
    
    // val lists = List(List(1, 2), List(1))
    for {
      outer <- lists
      inner <- outer.tap(println)
    } yield inner
    // List(2, 4, 6)
    // List(4, 8, 12)
    // ls: List[Int] = List(4, 8, 12)