scala 猫遍历列表

scala cats traverse for list

我正在尝试使用此页面了解列表的遍历, https://www.scala-exercises.org/cats/traverse

我有一个非常基本的问题(对于那些认为它太简单或太明显的人,我深表歉意)。 只需检查下面的签名

trait Traverse[F[_]] {
  def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
}
import cats.data.{ NonEmptyList, OneAnd, Validated, ValidatedNel }
import cats.implicits._

def parseIntEither(s: String): Either[NumberFormatException, Int] =
  Either.catchOnly[NumberFormatException](s.toInt)
List("1", "abc", "3").traverse(parseIntEither).isLeft should be(true)

你可以看到签名遍历需要 2 个参数,第一个参数是列表,第二个参数是 'f'。 那么为什么上面只用 1 个参数调用它?相反,列表变成了调用遍历方法的 'instance'/'object'。

我很纳闷

非常感谢。

因为如果你有(n+1)-参数方法foo

class Arg1
class Arg2
  
object Obj {
  def foo(arg1: Arg1, arg2: Arg2) = ???
}

您可以定义一个语法(扩展方法,例如具有相同名称foo)将调用委托给前者foo

implicit class Arg1Ops(val arg1: Arg1) extends AnyVal {
  def foo(arg2: Arg2) = Obj.foo(arg1, arg2)
}

    // which is basically the same as
//  class Arg1Ops(val arg1: Arg1) extends AnyVal {
//    def foo(arg2: Arg2) = Obj.foo(arg1, arg2)
//  }
//
//  implicit def toArg1Ops(arg1: Arg1): Arg1Ops = new Arg1Ops(arg1)

并作为调用者在第一个参数上调用 foo 作为 n 参数方法

val a1 = new Arg1
val a2 = new Arg2

a1.foo(a2)

在你的例子中 List("1", "abc", "3") 就像 arg1parseIntEither 就像 arg2.

在猫中 Traverse 被注释为 Simulacrum macro-annotation @typeclass

Traverse.scala

@typeclass trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[F] { ...

并且此注释生成 Traverse

的语法

syntax/package.scala#L64

object traverse extends TraverseSyntax

syntax/traverse.scala

trait TraverseSyntax extends Traverse.ToTraverseOps

Traverse.scala

object Traverse extends scala.AnyRef with java.io.Serializable {
  ...

  // macro-generated code
  trait Ops[F[_], C] extends scala.AnyRef {
    ...
    def traverse[G[_], B](f : scala.Function1[C, G[B]])(implicit 
      evidence : cats.Applicative[G]
    ) : G[F[B]] = { /* compiled code */ }
    ...
  }

  // macro-generated code
  trait ToTraverseOps extends scala.AnyRef {
    @...
    implicit def toTraverseOps[F[_], C](target : F[C])(implicit 
      tc : cats.Traverse[F]
    ) : Traverse.Ops[F, C] { type TypeClassType = cats.Traverse[F] } = 
    { /* compiled code */ }
  }

  ...

}

考虑以下等效的调用方式traverse

implicitly[Traverse[List]].traverse(List("1", "abc", "3"))(parseIntEither)  // scary
Traverse.apply[List].traverse(List("1", "abc", "3"))(parseIntEither)        // a bit better
Traverse[List].traverse(List("1", "abc", "3"))(parseIntEither)              // much better
List("1", "abc", "3").traverse(parseIntEither)                              // what I ideally want to write

在所有情况下,我们都需要访问 List 满足 Traverse 类型 class 约束的证据,然后才能调用 traverse。我们可以像第一种情况那样使用 implicitly 来非常明确地说明它,但理想情况下我们只想对一个值调用 traverse 而不必过多担心类型 class 机制,如最后一个案例。最后一个案例使用了名为 扩展方法 的 Scala 工具。 Scala 3 有一些 new syntax,IMO 真正阐明了扩展概念。在 Scala 2 中,它们是使用 implicit class 结构实现的,对于初次接触该概念的初学者来说,作为关键字可能不是很有信息量。在 Scala 3 中,扩展方法的概念可以直接与类型 class 的概念相关联,也许类似于 so

trait Traverse[F[_]]:
  extension [G[_]: Applicative, A, B](fa: F[A]) def traverse(f: A => G[B]): G[F[B]]