refined icon indicating copy to clipboard operation
refined copied to clipboard

`Greater[5] Or Equal[5]` results in drastic performance problems with `unsafeFrom`

Open toddburnside opened this issue 1 year ago • 8 comments

Running the following takes more than a minute to complete for me:

type Crazy = Int Refined (Greater[5] Or Equal[5])
object Crazy extends RefinedTypeOps.Numeric[Crazy, Int]
val crazy = Crazy.unsafeFrom(5)

However, the following is fine:

val sane: Crazy = refineV[Greater[5] Or Equal[5]](5).toOption.get

Here is a scastie demonstrating the problem: https://scastie.scala-lang.org/oGPpPS59RcG8ejX9dSDhhQ It just gets closed, but when run locally it will eventually return.

I have tried refined versions 0.9.29 through 0.10.2 and scala versions 2.13.8 and 3.2.2

toddburnside avatar Mar 10 '23 21:03 toddburnside

I suspect that calculating MinValue and MaxValue of Crazy is the culprit.

fthomas avatar Mar 10 '23 21:03 fthomas

That was quick! I should have mentioned in the comment, Equal[5] Or Greater[5] is fine.

toddburnside avatar Mar 10 '23 21:03 toddburnside

It seems @fthomas suspicions are correct. This does it:

object Main extends App {
  type Crazy = Int Refined (Greater[5] Or Equal[5])
  val min: Min[Crazy] = implicitly[Min[Crazy]]
}

steinybot avatar Mar 13 '23 07:03 steinybot

Oh Or is going to use Min.validateMin which will start at Int.MinValue and step up one at a time until it finds a valid value. No wonder.

Would something like this work?

  implicit def orMin[F[_, _], T, L, R](implicit
      rt: RefType[F],
      ml: Min[F[T, L]],
      mr: Min[F[T, R]],
      at: Adjacent[T],
      v: Validate[T, L Or R]
  ): Min[F[T, L Or R]] =
    Min.instance(rt.unsafeWrap(findValid(at.min(rt.unwrap(ml.min), rt.unwrap(mr.min)))))

steinybot avatar Mar 13 '23 07:03 steinybot

Would something like this work?

Yes, I think so. But since this is Or, the findValid call should not be needed.

fthomas avatar Mar 13 '23 07:03 fthomas

Oh good point. I realised that still isn't going to cut it. There isn't one for Equal.

This seems to fix it:

  implicit def orMin[F[_, _], T, L, R](implicit
      rt: RefType[F],
      ml: Min[F[T, L]],
      mr: Min[F[T, R]],
      at: Adjacent[T],
      v: Validate[T, L Or R]
  ): Min[F[T, L Or R]] =
    Min.instance(rt.unsafeWrap(at.min(rt.unwrap(ml.min), rt.unwrap(mr.min))))
  
  implicit def equalMin[F[_, _], T, N](implicit
      rt: RefType[F],
      wn: WitnessAs[N, T]
  ): Min[F[T, Equal[N]]] =
    Min.instance(rt.unsafeWrap(wn.snd))

steinybot avatar Mar 13 '23 07:03 steinybot

It probably makes sense to add these two instances for Min and Max.

On a side note: The built-in GreaterEqual[N] should be preferred over Greater[N] Or Equal[N].

fthomas avatar Mar 13 '23 16:03 fthomas

On a side note: The built-in GreaterEqual[N] should be preferred over Greater[N] Or Equal[N].

Agreed. I found this example/problem in a different library and I'm putting in a PR to do just that. 😄

toddburnside avatar Mar 13 '23 17:03 toddburnside