理解 cats-effect 中 forceR 的定义?

huangapple go评论68阅读模式
英文:

Understanding cats-effect forceR definition?

问题

def forceR[B](that: IO[B]): IO[B] =
    // 强制转换在这里是必要的,以欺骗编译器,避免产生 IO[Any]
    asInstanceOf[IO[Unit]].handleError(_ => ()).productR(that)

来源

  • 自身的 asInstanceOf[IO[Unit]] 将自身转换为 IO[Unit]
  • 然后 handleError 的类型为 def handleError[B >: A](f: Throwable => B): IO[B],通常情况下,返回的 IO[B] 的类型约束应该是 IO[Unit] 的超类型

那么为什么调用站点上的 forceR 定义具有真正的 IO[B] 类型,例如 IO[Int]

英文:
def forceR[B](that: IO[B]): IO[B] =
    // cast is needed here to trick the compiler into avoiding the IO[Any]
    asInstanceOf[IO[Unit]].handleError(_ => ()).productR(that)

Source

  • Self asInstanceOf[IO[Unit]] turns self into a IO[Unit]
  • and then handleError has the type def handleError[B >: A](f: Throwable => B): IO[B] which normally stats the type bound of returning IO[B] should be super type of IO[Unit]

Then why this forceR definition on the call site have the true IO[B] type such as IO[Int]?

答案1

得分: 3

Your question is slightly confusing.

> Then why this forceR definition on the call site has the true IO[B] type such as IO[Int]?

Because the returned type of forceR is specified to be IO[B]. So it can't be anything else at the call site (if the code compiles).

I guess you mis-interprete the comment

// cast is needed here to trick the compiler into avoiding the IO[Any]

This is not to avoid return type IO[Any]. The return type will anyway be IO[B] because of productR, @GaëlJ is correct

  ...

  def productR[B](that: IO[B]): IO[B] =
    flatMap(_ => that)

So this is not intended to make this.handleError(_ => ()).productR(that) not IO[Any], this seems to be intended to make this.handleError(_ => ()) not IO[Any].

If this is IO[A] and A <: AnyVal then this.handleError(_ => ()) has type IO[AnyVal]. If not A <: AnyVal then this.handleError(_ => ()) has type IO[Any]. Not sure why it's important in handleError though. Some reflection (.getClass), caching, stack tracing is used in handleError. Maybe error message is undesired for IO[Any].

  handleErrorWith[B](t => IO.pure(f(t)))

def handleErrorWith[B >: A](f: Throwable => IO[B]): IO[B] =
  IO.HandleErrorWith(this, f, Tracing.calculateTracingEvent(f))

def calculateTracingEvent(key: Any): TracingEvent = {
  if (isCachedStackTracing) {
    val cls = key.getClass
    get(cls)
  } else if (isFullStackTracing) {
    buildEvent()
  } else {
    null
  }
}

/**
 * Holds platform-specific flags that control tracing behavior.
 *
 * <p>The Scala compiler inserts a volatile bitmap access for module field accesses. Because the
 * `tracingMode` flag is read in various IO constructors, we are opting to define it in a Java
 * source file to avoid the volatile access.
 *
 * <p>INTERNAL API with no source or binary compatibility guarantees.
 */
public final class TracingConstants {...}

Otherwise maybe casting was just a precaution and is not actually necessary 理解 cats-effect 中 forceR 的定义?

The specific commit where @DanielSpiewak
added the casting was here

https://github.com/typelevel/cats-effect/commit/66d30c4ccdfdb8aca5f6632f5506dda192fc2f03#diff-2dec4bb5f2b890f966e62cfdfcc6d32746235e45b4a14113f56e38b49923132aL367-R368


The interesting question is why actually Daniel needed not IO[Any] in the part before productR and whether he actually needed that. I'm not 100% sure.

> @LuisMiguelMejíaSuárez:
>
> This is just speculation, but I am fairly sure is just to allow compilation in the presence of strict compiler flags. As you may know, there is a flag to make the compiler warn if it infers Any, and another flag that turns all compiler warning into errors; thus stopping compilation. Additionally, as you may also be aware the sbt-tpolecat plugin enable those warnings and I am pretty sure most typelevel project either use that plugin, or enable a similar set of flags by other means. So, that is why they needed to avoid the inferred Any while recovering from the possible errors.

Makes sense.

英文:

Your question is slightly confusing.

> Then why this forceR definition on the call site have the true IO[B] type such as IO[Int]?

Because the returned type of forceR is specified to be IO[B]. So it can't be anything else at the call site (if the code compiles).

I guess you mis-interprete the comment

// cast is needed here to trick the compiler into avoiding the IO[Any]

This is not to avoid return type IO[Any]. The return type will anyway be IO[B] because of productR, @GaëlJ is correct

sealed abstract class IO[+A] ... {
  ...

  def productR[B](that: IO[B]): IO[B] =
    flatMap(_ => that)

So this is not intended to make this.handleError(_ => ()).productR(that) not IO[Any], this seems to be intended to make this.handleError(_ => ()) not IO[Any].

If this is IO[A] and A <: AnyVal then this.handleError(_ => ()) has type IO[AnyVal]. If not A <: AnyVal then this.handleError(_ => ()) has type IO[Any]. Not sure why it's important in handleError though. Some reflection (.getClass), caching, stack tracing is used in handleError. Maybe error message is undesired for IO[Any].

def handleError[B >: A](f: Throwable => B): IO[B] =
  handleErrorWith[B](t => IO.pure(f(t)))

def handleErrorWith[B >: A](f: Throwable => IO[B]): IO[B] =
  IO.HandleErrorWith(this, f, Tracing.calculateTracingEvent(f))

def calculateTracingEvent(key: Any): TracingEvent = {
  if (isCachedStackTracing) {
    val cls = key.getClass
    get(cls)
  } else if (isFullStackTracing) {
    buildEvent()
  } else {
    null
  }
}

/**
 * Holds platform-specific flags that control tracing behavior.
 *
 * <p>The Scala compiler inserts a volatile bitmap access for module field accesses. Because the
 * `tracingMode` flag is read in various IO constructors, we are opting to define it in a Java
 * source file to avoid the volatile access.
 *
 * <p>INTERNAL API with no source or binary compatibility guarantees.
 */
public final class TracingConstants {...}

Otherwise maybe casting was just a precaution and is not actually necessary 理解 cats-effect 中 forceR 的定义?

The specific commit where @DanielSpiewak
added the casting was here

https://github.com/typelevel/cats-effect/commit/66d30c4ccdfdb8aca5f6632f5506dda192fc2f03#diff-2dec4bb5f2b890f966e62cfdfcc6d32746235e45b4a14113f56e38b49923132aL367-R368


The interesting question is why actually Daniel needed not IO[Any] in the part before productR and whether he actually needed that. I'm not 100% sure.

> @LuisMiguelMejíaSuárez:
>
> This is just speculation, but I am fairly sure is just to allow compilation in the presence of strict compiler flags. As you may know, there is a flag to make the compiler warn if it infers Any, and another flag that turns all compiler warning into errors; thus stopping compilation. Additionally, as you may also be aware the sbt-tpolecat plugin enable those warnings and I am pretty sure most typelevel project either use that plugin, or enable a similar set of flags by other means. So, that is why they needed to avoid the inferred Any while recovering from the possible errors.

Makes sense.

huangapple
  • 本文由 发表于 2023年4月17日 10:29:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76031360.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定