cats-effect icon indicating copy to clipboard operation
cats-effect copied to clipboard

Add different effect init for `Random`/`SecureRandom`

Open geny200 opened this issue 5 months ago • 4 comments
trafficstars

I would like to be able to initialize Random in a different effect, as it's in Ref and Deffered (def in[F, G, A]). Motivation:

  • I can't use transformer implicits, because I want to initialize Random, for example, in ConnectionIO (type from Doobie, which has implementation of Sync for ConnectionIO).
  • I don't want to use mapK because it would be incorrect and expensive for implementation (ex: provided by lib mapK for ConnectionIO uses a Dispatcher that runs the logic, which adds additional costs for lift and unlift from IO)

Below is a sample code for Ref, I would like to be able to do exactly the same for Random and SecureRandom.

  import cats.FlatMap
  import cats.effect.std.Console
  import cats.effect.{Ref, Sync}
  import cats.syntax.all.*

  class Foo[F[_]: FlatMap: Console](ref: Ref[F, Int]) {
    def print: F[Unit] = ref.get.flatMap(Console[F].println)
  }

  def foo[I[_]: Sync, F[_]: Sync: Console]: I[Foo[F]] =
    for {
      ref <- Ref.in[I, F, Int](0)
      /// Init some classes in F
    } yield new Foo(ref)

Proposal:

I looked at current implementation javaSecuritySecureRandom, and it can be divided into 2 effects. So, new method will have signature like this:

// Proposal - add new method
def javaSecuritySecureRandomIn[I[_]: Sync, F[_]: Sync](n: Int): I[SecureRandom[F]]

// Current impl
def javaSecuritySecureRandom[F[_]: Sync](n: Int): F[SecureRandom[F]]

geny200 avatar May 22 '25 13:05 geny200

Someone recently opened a PR for this, but I think it needs to be finished up:

  • https://github.com/typelevel/cats-effect/pull/4338

I don't want to use mapK because it would be incorrect and expensive for implementation (ex: provided by lib mapK for ConnectionIO uses a Dispatcher that runs the logic, which adds additional costs for lift and unlift from IO)

How would it be incorrect?

Regarding performance: actually,ConnectionIO's liftK will not use a Dispatcher in this case, since the entire operation can be directly interpreted with syncStep.

  • https://github.com/typelevel/doobie/pull/1906

armanbilge avatar May 22 '25 14:05 armanbilge

be directly interpreted with syncStep

It dosn't work for effects ex (result - "Left"):

import cats.effect.unsafe.implicits.global
import cats.effect.{Async, IO}

import java.util.UUID

Async[IO].syncStep[IO, UUID](IO.randomUUID, Int.MaxValue).flatMap {
  case Left(_)  => IO.println(s"Left") // Can't run - use Dispatcher
  case Right(_) => IO.println(s"Right")
}.unsafeRunSync()

Someone recently opened a PR for this, but I think it needs to be finished up:

Thx, I didn't see it (I watched only issue). I could complete that PR, but it would be difficult to access (branch is in a personal fork). Therefore, I suggest that I open a new MR with suggested edits and comments fixses from previous PR. (before that, I'll ask author if he's ready to finish PR, or if he'll let me finish it)

geny200 avatar May 22 '25 16:05 geny200

It dosn't work for effects ex (result - "Left"):

Hmm, are you sure? This program prints "Right" for me.

//> using dep org.typelevel::cats-effect::3.6.1

import cats.effect.*

import java.util.UUID

object App extends IOApp.Simple:
  def run = Async[IO]
    .syncStep[IO, UUID](IO.randomUUID, Int.MaxValue)
    .flatMap {
      case Left(_)  => IO.println(s"Left") // Can't run - use Dispatcher
      case Right(_) => IO.println(s"Right")
    }

Therefore, I suggest that I open a new MR with suggested edits and comments fixses from previous PR.

Yes, that's the way to go, thanks!

armanbilge avatar May 23 '25 05:05 armanbilge

Hmm, are you sure?

Yes) For me it's Left... Ok, i'll try to minimize that behavior (for me - run in sbt, jvm temurin 21, windows 11); some of my friends say that they have Left, and some have Right

geny200 avatar May 23 '25 15:05 geny200