【问题标题】:Scalaz: how does `scalaz.syntax.applicative._` works its magicScalaz:“scalaz.syntax.applicative._”如何发挥它的魔力
【发布时间】:2016-12-02 00:03:41
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: scala scalaz applicative reader-monad


    【解决方案1】:

    当您试图弄清楚这样的事情时,第一步是使用反射 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 中称为“丰富”)。从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 告诉我们它找到的是 @987654345 @。到那时它就完成了,我们得到了我们想要的ReaderInt[String]

    【讨论】:

    • 我不知道反射 API。好的。尽管我觉得这两个答案都很好,但我会接受你教我如何钓鱼的答案;)
    【解决方案2】:

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

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

    【讨论】:

    • 感谢您添加此说明。一些小评论: - 我猜你的意思是确实有方法point。 -sequence 的隐式版本中还缺少一个]
    • 是的,抱歉搞砸了一点:)。它们都是别名,因此在这种情况下,您可以互换使用它们。我更新了帖子
    猜你喜欢
    • 1970-01-01
    • 2012-04-21
    • 1970-01-01
    • 2011-10-15
    • 2012-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多