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
实例。在您的情况下,您已经明确指定 F
是 ReaderInt
。它将别名解析为 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"))
这个问题与
在答案中,作者使用以下代码获取 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
实例。在您的情况下,您已经明确指定 F
是 ReaderInt
。它将别名解析为 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"))