Scala cats.Random

116 views Asked by At

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.

1

There are 1 answers

0
slouc On BEST ANSWER

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] = ...