Scalaz: как работает "scalaz.syntax.applicative._`"
Этот вопрос связан с этим, где я пытался понять, как использовать читалку в Scala.
В ответе автор использует следующий код для получения экземпляра ReaderInt[String]
:
import scalaz.syntax.applicative._
val alwaysHello2: ReaderInt[String] = "hello".point[ReaderInt]
Какие механизмы использует Scala для определения типа выражения "hello".point[ReaderInt]
так что он использует право point
функционировать?
2 ответа
Хороший первый шаг каждый раз, когда вы пытаетесь понять что-то вроде этого, это использовать 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
- он начинает искать неявные преобразования, которые бы преобразовали String
в тип, который имеет соответствие point
метод (это называется "обогащение" в Scala). Мы можем видеть из вывода showCode
что неявное преобразование, которое он находит, является методом, называемым ApplicativeIdV
в applicative
Синтаксис объекта.
Затем он применяет это преобразование к 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]
потому что скала не смогла выяснить соответствующие Int
тип для этого аппликативного. Другими словами, он не знал Applicative, для чего нужно вводить Reader (хотя иногда компилятор может понять это)
Для Applicative с одним параметром типа мы можем привести неявные экземпляры, просто используя 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
, только возвращает соответствующий 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
и начинает просматривать последствия, как он может получить что-то, что имеет point
, от String
,
Он находит, что может конвертировать 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"))