refined icon indicating copy to clipboard operation
refined copied to clipboard

Size predicate validator error

Open DenisNovac opened this issue 2 years ago • 7 comments

Lib version: "0.10.1"

There is no validator for types refined with Size[n] predicate. Which leads to an error:

could not find implicit value for parameter v: eu.timepit.refined.api.Validate[Long, T]

In "Practical FP in Scala" book there is this workaround (SizeValidator on the code), but it doesn't work for me anyway with error:

exception during macro expansion: java.lang.ClassNotFoundException: Playground$Test2$ at scala.reflect.internal.util.AbstractFileClassLoader.findClass(AbstractFileClassLoader.scala:75) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) at __wrapper$3$9ef546e2ad714e8c98288e38cb01ef12.__wrapper$3$9ef546e2ad714e8c98288e38cb01ef12$.wrapper(:31) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.$anonfun$compile$11(ToolBoxFactory.scala:291) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:460) at scala.reflect.macros.contexts.Evals.eval(Evals.scala:32) at scala.reflect.macros.contexts.Evals.eval$(Evals.scala:26) at scala.reflect.macros.contexts.Context.eval(Context.scala:18) at eu.timepit.refined.macros.MacroUtils.$anonfun$eval$1(MacroUtils.scala:22) at scala.Option.getOrElse(Option.scala:201) at eu.timepit.refined.macros.MacroUtils.tryN(MacroUtils.scala:26) at eu.timepit.refined.macros.MacroUtils.tryN$(MacroUtils.scala:25) at eu.timepit.refined.macros.RefineMacro.tryN(RefineMacro.scala:10) at eu.timepit.refined.macros.MacroUtils.eval(MacroUtils.scala:22) at eu.timepit.refined.macros.MacroUtils.eval$(MacroUtils.scala:15) at eu.timepit.refined.macros.RefineMacro.eval(RefineMacro.scala:10) at eu.timepit.refined.macros.RefineMacro.$anonfun$validateInstance$2(RefineMacro.scala:53) at scala.Option.getOrElse(Option.scala:201) at eu.timepit.refined.macros.RefineMacro.validateInstance(RefineMacro.scala:53) at eu.timepit.refined.macros.RefineMacro.impl(RefineMacro.scala:25)

import Typ._
import eu.timepit.refined.api.{Refined, Validate}
import eu.timepit.refined.auto._
import eu.timepit.refined.collection.Size

trait SizeValidator {
  implicit def validateSizeN[N <: Int, R](implicit
                                          w: ValueOf[N]
                                         ): Validate.Plain[R, Size[N]] =
    Validate.fromPredicate[R, Size[N]](
      _.toString.length == w.value,
      _ => s"Must have ${w.value} digits",
      Size[N](w.value)
    )
}

object Example extends App {
  println("Hello")
  println(Test2.x)
}

object Typ {
  type SixP  = Int Refined Size[6]
}

// won't work at all because validator not found
/*object Test1 {
  val y: SixP = 123
  val x: SixP = refineV[SixP](123).toOption.get
}*/

// will throw macros error
object Test2 extends SizeValidator {
  val x: SixP = 123
}

Here is scastie runnable example: https://scastie.scala-lang.org/frNAMizRRpaTBaoeJjjrEQ

DenisNovac avatar Feb 26 '23 15:02 DenisNovac

Try Size[Equal[n]].

fthomas avatar Feb 26 '23 15:02 fthomas

Validator is still missing.

I also tried to rewrite self-written validator with Equal but the macros error is still here.

  implicit def validateSizeN[N <: Int, R](implicit
                                          w: ValueOf[N]
                                         ): Validate.Plain[R, Size[Equal[N]]] =
    Validate.fromPredicate[R, Size[Equal[N]]](
      _.toString.length == w.value,
      _ => s"Must have ${w.value} digits",
      Size[Equal[N]](Equal[N](w.value))
    )

DenisNovac avatar Feb 26 '23 15:02 DenisNovac

Validator is still missing.

Right. You're trying to check if the string representation of an Int has n digits. We don't have a predicate for that.

I also tried to rewrite self-written validator with Equal but the macros error is still here.

Looks like https://github.com/fthomas/refined/blob/master/modules/docs/macro_pitfalls.md. Custom predicates + macros probably won't work in Scastie. refineV works: https://scastie.scala-lang.org/3nGUIQ9tSdKGmQ2lNZE8fw

fthomas avatar Feb 26 '23 15:02 fthomas

They are not working in the IDEA either. Tried to write exact types instead type aliases and it helped. Thanks for helping out!

I didn't really got what it is about in macro_pitfalls. I should replace my SixP alias and validate instance to a different project so i could use refineV directly on it instead of Int Refined Size[6]?

DenisNovac avatar Feb 26 '23 16:02 DenisNovac

I didn't really got what it is about in macro_pitfalls.

My recommendation is to not use refined's macros at all. They cause problems like above and are not available on Scala 3.

I should replace my SixP alias and validate instance to a different project so i could use refineV directly on it instead of Int Refined Size[6]?

No, the alias is fine. If you don't use macros, there is also no need to move the Validate instance to another project.

What I've been doing lately is to define a type alias + an object with the same name that extends RefinedTypeOps like this:

import eu.timepit.refined.api.RefinedTypeOps

type SixP = Int Refined Size[6]
object SixP extends RefinedTypeOps[Int Refined Size[6], Int]

which gives you nice syntax for creating SixP instances, like SixP.from(123456). Here is a Scastie that demonstrates this: https://scastie.scala-lang.org/njwd5esnRpe6daeNizS0HQ

fthomas avatar Feb 27 '23 14:02 fthomas

Got it, thanks for clarifying! Is there any chance of having Validate for Size predicate in refined out of the box?

DenisNovac avatar Feb 27 '23 15:02 DenisNovac

There is already a Validate instance for Size in the library but it requires that your base type is Iterable. For example, this would work out of the box: String Refined Size[Equal[6]] // strings with length 6. But your base type is Int (which is not Iterable), so the instance in the library does not match. I guess one could write a higher-order predicate that checks if the string representation matches some predicate. Something like final case class ToString[P](p: P) which could be used to define your SixP like this: Int Refined ToString[Size[Equal[6]]. I'd be open to add ToString to the library.

fthomas avatar Feb 27 '23 15:02 fthomas