Scalaz:`scalaz.syntax.applicative._` 是如何发挥其魔力的

Scalaz: how does `scalaz.syntax.applicative._` works its magic

这个问题与 有关,我试图了解如何在 Scala 中使用 reader monad。

在答案中,作者使用以下代码获取 ReaderInt[String] 的实例:

import scalaz.syntax.applicative._
val alwaysHello2: ReaderInt[String] = "hello".point[ReaderInt]

Scala 使用哪些机制来解析表达式 "hello".point[ReaderInt] 的类型,以便它使用正确的 point 函数?

任何时候当你试图弄清楚这样的事情时,一个很好的第一步是使用反射 API 来去除表达式的糖分:

scala> import scalaz.Reader, scalaz.syntax.applicative._
import scalaz.Reader
import scalaz.syntax.applicative._

scala> import scala.reflect.runtime.universe.{ reify, showCode }
import scala.reflect.runtime.universe.{reify, showCode}

scala> type ReaderInt[A] = Reader[Int, A]
defined type alias ReaderInt

scala> showCode(reify("hello".point[ReaderInt]).tree)
res0: String = `package`.applicative.ApplicativeIdV("hello").point[$read.ReaderInt](Kleisli.kleisliIdMonadReader)

(您通常不想在实际代码中使用 scala.reflect.runtime,但它对于像这样的调查非常方便。)

当编译器发现您试图在没有 point 方法的类型上调用 .point[ReaderInt] 时(在本例中为 String),它开始寻找隐式转换会将 String 转换为具有匹配 point 方法的类型(这在 Scala 中称为 "enrichment")。从showCode的输出我们可以看出,它找到的隐式转换是applicative语法对象中一个叫做ApplicativeIdV的方法

然后将此转换应用到 String,生成 ApplicativeIdV[String] 类型的值。这种类型的 point 方法如下所示:

def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)

像这样的东西是语法糖:

def point[F[_]](implicit F: Applicative[F]): F[A] = F.point(self)

所以接下来需要做的是为 F 找到一个 Applicative 实例。在您的情况下,您已经明确指定 FReaderInt。它将别名解析为 Reader[Int, _],它本身就是 Kleisli[Id.Id, Int, _] 的别名,并开始寻找实例。

它首先查找的地方之一是 Kleisli 伴随对象,因为它需要一个包含 Kleisli 的类型的隐式值,实际上 showCode 告诉我们它找到的那个是 Kleisli.kleisliIdMonadReader。到那时它就完成了,我们得到了我们想要的ReaderInt[String]

我想更新以前的答案,但由于您创建了单独的问题,所以我把它放在这里。

scalaz.syntax

让我们考虑 point 示例,您可以将相同的推理应用于其他方法。

point(或haskell的return)或pure(只是一个类型别名)属于Applicative特征。如果你想把一些东西放在一些 F 里面,你至少需要 Applicative 这个 F 的实例。

通常,您会通过导入隐式提供它,但您也可以显式指定它。

在第一个问题的示例中,我将其分配给 val

implicit val KA = scalaz.Kleisli.kleisliIdApplicative[Int]

因为 scala 无法找出该应用程序对应的 Int 类型。换句话说,它不知道 Applicative for 引入哪个 Reader。(尽管有时编译器可以弄清楚)

对于只有一个类型参数的Applicatives,我们可以通过import

引入隐式实例
import scalaz.std.option.optionInstance 
import scalaz.std.list.listInstance

等...

好的,你有实例。现在您需要对其调用 point 。 你有几个选择:

1.直接访问方式

scalaz.std.option.optionInstance.point("hello")
KA.pure("hello")

2。从隐式上下文中显式提取它:

Applicative[Option].point("hello") 

如果您查看 Applicative 对象,您会看到

object Applicative {
  @inline def apply[F[_]](implicit F: Applicative[F]): Applicative[F] = F
}

apply 的实现仅返回某种类型 F 的相应 Applicative[F] 实例。

所以Applicative[Option].point("hello")转换为 Applicative[Option].apply(scalaz.std.option.optionInstance) 最后就是 optionInstance

3。使用语法

import scalaz.syntax.applicative._ 

将此方法引入隐式范围:

  implicit def ApplicativeIdV[A](v: => A) = new ApplicativeIdV[A] {
    val nv = Need(v)
    def self = nv.value
  }

  trait ApplicativeIdV[A] extends Ops[A] {
    def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)
    def pure[F[_] : Applicative]: F[A] = Applicative[F].point(self)
    def η[F[_] : Applicative]: F[A] = Applicative[F].point(self)
  }  ////

那么,每当您尝试在 String

上调用 point
"hello".point[Option] 

编译器意识到 String 没有方法 point 并开始查看隐含函数,它如何从 String 中获得具有 point 的内容.

它发现,它可以将String转换为ApplicativeIdV[String],这确实有方法point:

 def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)

所以最后 - 你的电话脱糖到

new ApplicativeIdV[Option]("hello")


scalaz 中的所有类型类或多或少都以相同的方式工作。 对于 sequence,实现是

def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
    traverse(fga)(ga => ga)

G 之后的这个冒号表示应该隐式提供 Applicative[G]。 它本质上是一样的:

def sequence[G[_], A](fga: F[G[A]])(implicit ev: Applicative[G[_]]): G[F[A]] =
        traverse(fga)(ga => ga)

因此,您只需要 Applicative[G] 和 Traverse[F]。

import scalaz.std.list.listInstance
import scalaz.std.option.optionInstance
Traverse[List].sequence[Option, String](Option("hello"))