Scalaz:“scalaz.syntax.applicative._”如何发挥其魔力



这个问题与这个问题有关,我试图理解如何在Scala中使用阅读器monad。

在答案中,autor 使用以下代码来获取 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方法的类型(在本例中为String(上调用.point[ReaderInt]时,它会开始寻找隐式转换,这些转换会将String转换为具有匹配point方法的类型(这在 Scala 中称为"扩充"(。我们可以从 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)

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

它看起来的第一个地方将是Kleisli伴侣对象,因为它想要一个包含 Kleisli 的类型的隐式值,实际上showCode告诉我们它找到的那个是 Kleisli.kleisliIdMonadReader .在这一点上,它完成了,我们得到了我们想要的ReaderInt[String]

我想更新前一个答案,但由于您创建了单独的问题,因此我将其放在这里。

scalaz.syntax

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

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

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

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

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

因为 Scala 无法找出此应用程序的相应Int类型。换句话说,它不知道该为哪个读者带来应用。(虽然有时编译器可以弄清楚(

对于具有一个类型参数的应用程序,我们可以仅通过使用导入来引入隐式实例

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") 

如果你研究应用对象,你会看到

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)

所以你所需要的只是应用[G]和遍历[F]。

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

相关内容

  • 没有找到相关文章

最新更新