I'm trying to learn functional random from cats. This is method inside big class:
def prepareDate(order: Model, zoneId: String): F[Instant] =
Random[F].betweenLong(7200, 21600).flatMap { seconds =>
order.dateTime
.plusSeconds(seconds)
.toInstant(ZoneOffset.of(zoneId))
.asInstanceOf[F[Instant]]
}
it not compiles with No given instance of type cats.effect.std.Random[F] was found for parameter ev of method apply in object Random.
error
I've tried to put Random inside this way:
class SomeClass[F[_]: Async: Logger: Random]
but have no idea how to implicit it in object-companion.
Let's remove some of the fluff, since the logic itself is irrelevant for the problem:
def prepareDate(): F[Long] =
Random[F].betweenLong(7200, 21600)
So... What's F[_]
? Compiler will ask you the same question.
If you had some concrete instance of F[_]
, for example cats.IO
, then the whole thing would be simple. You would just need to obtain an instance of Random[IO]
to work with, which can be done with e.g. Random.scalaUtilRandom[IO]
(this gives an IO[Random[IO]]
hence the flatMap
):
def prepareDate(): IO[Long] = {
Random.scalaUtilRandom[IO].flatMap(_.betweenLong(7200, 21600))
}
So what your question really comes down to is - given that you don't know what the user of prepareDate
will be using as F[_]
(it could be IO
or something else), how do you intend to prove that there will be a Random[F]
available in scope?
Your F[_]
is probably a generic parameter in the context where your prepareDate
method is defined. In case it's a class, then what you wrote would be correct - class SomeClass[F[_]: Random]
means that F
has to be a higher-kinded type (due to [_]
) and there has to be a Random[F]
available in scope. Whoever is instantiating a concrete instance of SomeClass
with some concrete F[_]
will have to make sure that Random[F]
is in scope.
Here's some complete code:
import cats.effect.{ExitCode, IO, IOApp}
import cats.effect.std.Random
object Main extends IOApp {
class MyDate[F[_]: Random] {
def prepareDate(): F[Long] =
Random[F].betweenLong(7200, 21600)
}
def run(args: List[String]): IO[ExitCode] = {
Random.scalaUtilRandom[IO].flatMap { implicit random =>
val myDate = new MyDate[IO]
myDate.prepareDate().flatMap(IO.println).as(ExitCode.Success)
}
}
// prints e.g. 17656
}
You mentioned having a companion object, but that doesn't change much in the concept itself - you just move "I need some F[_]
that has a Random[F]
in scope" from your class definition, to your method definition:
object MyDate {
def prepareDate[F[_]: Random](): F[Long] =
Random[F].betweenLong(7200, 21600)
}
def run(args: List[String]): IO[ExitCode] = {
Random.scalaUtilRandom[IO].flatMap { implicit random =>
MyDate.prepareDate()(random).flatMap(IO.println).as(ExitCode.Success)
}
}
Btw in case it helps you or someone else: [F[_] : Random]
is just syntactic sugar for the implicit parameter of type [Random[F]]
. This works equally fine, it's just more verbose:
def prepareDate[F[_]]()(implicit random: Random[F]): F[Long] = ...