这个问题与这个问题有关,我试图理解如何在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"))