Search code examples
scalascala-3cats-effect

Scala cats.Random


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.


Solution

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