4

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

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

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

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

4

2 回答 2

11

任何时候你试图弄清楚这样的事情的第一步是使用反射 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[ReaderInt]一个没有point方法的类型时——在这种情况下String——它开始寻找将 a 转换String为具有匹配point方法的类型的隐式转换(这在斯卡拉)。从输出中我们可以看出,showCode它找到的隐式转换是ApplicativeIdVapplicative语法对象中调用的方法。

然后它将这个转换应用于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)

因此,它需要做的下一件事是ApplicativeF. 在您的情况下,您已明确指定FReaderInt. 它将别名解析为Reader[Int, _],它本身就是 的别名Kleisli[Id.Id, Int, _],并开始寻找一个实例。

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

于 2016-07-27T20:56:42.240 回答
4

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

scalaz.syntax

让我们考虑这个point例子,你可以对其他方法应用相同的推理。

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

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

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

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

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

对于只有一个类型参数的 Applicatives,我们可以通过 import 引入隐式实例

import scalaz.std.option.optionInstance 
import scalaz.std.list.listInstance

ETC...

好的,你有实例。现在你需要调用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只是返回Applicative[F]某种类型的对应实例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)
  }  ////

因此,每当您尝试point调用String

"hello".point[Option] 

编译器意识到,String没有方法point并开始查看implicits,它如何能得到有的东西point,从String

它发现,它可以转换StringApplicativeIdV[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"))
于 2016-07-27T21:21:27.930 回答