问题
This question is related to this one, where I was trying to understand how to use the reader monad in Scala.
In the answer the autor uses the following code for getting an instance of ReaderInt[String]
:
import scalaz.syntax.applicative._
val alwaysHello2: ReaderInt[String] = "hello".point[ReaderInt]
Which mechanisms does Scala use to resolve the type of the expression "hello".point[ReaderInt]
so that it uses the right point
function?
回答1:
A good first step any time you're trying to figure out something like this is to use the reflection API to desugar the expression:
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)
(You generally don't want to use scala.reflect.runtime
in real code, but it's extremely handy for investigations like this.)
When the compiler sees you trying to call .point[ReaderInt]
on a type that doesn't have a point
method—in this case String
—it starts looking for implicit conversions that would convert a String
into a type that does have a matching point
method (this is called "enrichment" in Scala). We can see from the output of showCode
that the implicit conversion it finds is a method called ApplicativeIdV
in the applicative
syntax object.
It then applies this conversion to the String
, resulting in a value of type ApplicativeIdV[String]
. This type's point
method looks like this:
def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)
Which is syntactic sugar for something like this:
def point[F[_]](implicit F: Applicative[F]): F[A] = F.point(self)
So the next thing it needs to do is find an Applicative
instance for F
. In your case you've explicitly specified that F
is ReaderInt
. It resolves the alias to Reader[Int, _]
, which is itself an alias for Kleisli[Id.Id, Int, _]
, and starts looking for an instance.
One of the first places it looks will be the Kleisli
companion object, since it wants an implicit value of a type that includes Kleisli
, and in fact showCode
tells us that the one it finds is Kleisli.kleisliIdMonadReader
. At that point it's done, and we get the ReaderInt[String]
we wanted.
回答2:
I wanted to update the former answer, but since you created separate question, I put it here.
scalaz.syntax
Let's consider the point
example, and you can apply the same reasoning for other methods.
point
(or haskell's return
) or pure
(just a type alias) belongs to Applicative
trait. If you want to put something inside some F
, you need at least Applicative
instance for this F
.
Usually, you will provide it implicitly with imports, but you can specify it explicitly as well.
In example from the first question, I assigned it to val
implicit val KA = scalaz.Kleisli.kleisliIdApplicative[Int]
because scala was not able to figure out the corresponding Int
type for this applicative. In other words, it did not know Applicative for which Reader to bring in. (though sometimes compiler can figure it out)
For the Applicatives with one type parameter, we can bring implicit instances in just by using import
import scalaz.std.option.optionInstance
import scalaz.std.list.listInstance
etc...
Okay, you have the instance. Now you need to invoke point
on it.
You have few options:
1. Access method directly:
scalaz.std.option.optionInstance.point("hello")
KA.pure("hello")
2. Explicitly pull it from implicit context:
Applicative[Option].point("hello")
If you look into Applicative object, you would see
object Applicative {
@inline def apply[F[_]](implicit F: Applicative[F]): Applicative[F] = F
}
Implementation of apply
, is only returning the corresponding Applicative[F]
instance for some type F
.
So Applicative[Option].point("hello")
is converted to
Applicative[Option].apply(scalaz.std.option.optionInstance)
which in the end is just optionInstance
3. Use syntax
import scalaz.syntax.applicative._
brings this method into implicit scope:
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)
} ////
So then, whenever you try to invoke point
on a String
"hello".point[Option]
compiler realizes, that String
does not have the method point
and begins to look through implicits, how it can get something which has point
, from String
.
It finds, that it can convert String
to ApplicativeIdV[String]
, which indeed has method point
:
def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)
So in the end - your call desugares to
new ApplicativeIdV[Option]("hello")
More or less all typeclasses in scalaz are working the same way.
For sequence
, the implementation is
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
traverse(fga)(ga => ga)
This colon after G
means, that Applicative[G]
should be provided implicitly.
It is essentialy the same as:
def sequence[G[_], A](fga: F[G[A]])(implicit ev: Applicative[G[_]]): G[F[A]] =
traverse(fga)(ga => ga)
So all you need is the Applicative[G], and Traverse[F].
import scalaz.std.list.listInstance
import scalaz.std.option.optionInstance
Traverse[List].sequence[Option, String](Option("hello"))
来源:https://stackoverflow.com/questions/38622081/scalaz-how-does-scalaz-syntax-applicative-works-its-magic