refined icon indicating copy to clipboard operation
refined copied to clipboard

use Equal[_] with case object

Open bwiercinski opened this issue 5 years ago • 9 comments

I want to achieve:

sealed trait Status extends Product with Serializable
object Status {
  case object A extends Status
  case object B extends Status
  case object C extends Status
}

import shapeless.::
import shapeless.HNil
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.OneOf
import eu.timepit.refined.generic.Equal
import eu.timepit.refined.auto._

type ValidStatus = OneOf[Equal[Status.A.type] :: Equal[Status.B.type] :: HNil]

val validStatus: Status Refined ValidStatus = Status.A

and i get

<console>:22: error: compile-time refinement only works with literals
       val validStatus: Status Refined ValidStatus = Status.A

when i changed to

import eu.timepit.refined.auto.refineMV
val validStatus: Status Refined ValidStatus = refineMV[ValidStatus](Status.A)

i got

<console>:20: error: implicit error;
!I v: Validate[A.type, OneOf[Equal[A.type] :: Equal[B.type] :: HNil]]
OneOf.oneOfHConsValidate invalid because
!I vt: Validate.Aux[A.type, OneOf[Equal[B.type] :: HNil], OneOf[RT]]
――OneOf.oneOfHConsValidate invalid because
  !I vh: Validate.Aux[A.type, Equal[B.type], RH]
――――Equal.equalValidate invalid because
    !I wu: WitnessAs[B.type, A.type]
――――――WitnessAs.natWitnessAs invalid because
      !I ta: ToInt[B.type]
       val validStatus: Status Refined ValidStatus = refineMV[ValidStatus](Status.A)                                                                     

refineV also not working

bwiercinski avatar Feb 13 '20 10:02 bwiercinski

I think we can generalize this issue to that refineMV and refined.auto don't handle singleton objects, just literal singleton types (at least that's what I think it is).

kubukoz avatar Feb 13 '20 23:02 kubukoz

In fact, it doesn't work on literal singleton types either, just inline literals :/

kubukoz avatar Feb 25 '20 11:02 kubukoz

In fact, it doesn't work on literal singleton types either, just inline literals :/

@kubukoz Could you provide an example? The example below works as expected and is not using an "inline literal".

scala> val s = "Hello"
s: String = Hello

scala> refineMV[Equal[s.type]]("World")
<console>:37: error: Predicate failed: (World == Hello).
       refineMV[Equal[s.type]]("World")
                              ^

fthomas avatar Feb 25 '20 19:02 fthomas

@bwiercinski refineV works if the value you pass in is of type Status:

scala> refineV[ValidStatus](Status.A: Status)
res5: Either[String,eu.timepit.refined.api.Refined[Status,ValidStatus]] = Right(A)

scala> refineV[ValidStatus](Status.C: Status)
res6: Either[String,eu.timepit.refined.api.Refined[Status,ValidStatus]] = Left(Predicate failed: oneOf((C == A), (C == B), false).)

It will never work with refineMV because that macro needs to evaluate its value parameter at compile-time and that is only safe for literals. If the constructor of Status.A would contain side-effects, evaluating it at compile-time would be a bad idea.

fthomas avatar Feb 25 '20 19:02 fthomas

@fthomas but Status.A is a singleton object, its type is fully known at compile-time.

As for the literal singletons:

type X = String Refined Equal["foo"]

@ val a: X = "foo"
a: X = foo

@ val b: "foo" = "foo"
b: String = "foo"

@ val c: X = b
cmd11.sc:1: compile-time refinement only works with literals
val c: X = b
           ^
Compilation Failed

which is basically the opposite way (the literal is the value I want to refine, and in your example it's the type to refine to)

kubukoz avatar Feb 25 '20 20:02 kubukoz

Yes, but refineMV requires the value at compile-time to decide if it satisfies the predicate. And it is not safe to evaluate Status.A at compile-time since this would execute its constructor.

fthomas avatar Feb 25 '20 21:02 fthomas

Could we inspect the type of Status.A and use the fact that it's a singleton?

I tried doing this myself but I'm not that familiar with the scala-reflect API and couldn't figure that out soon enough...

kubukoz avatar Feb 25 '20 21:02 kubukoz

Oh, I see. That would probably be impossible in the general case (some validations will really require an instance). Equal is fine, but it's a special case.

However, for literal singleton types we could use the name of the type as the value.

kubukoz avatar Feb 25 '20 21:02 kubukoz

However, for literal singleton types we could use the name of the type as the value.

You're right. But I wouldn't recommend changing RefineMacro for this since it is 2.13-only and I'm not sure if RefineMacro will survive the transition to Scala 3.

fthomas avatar Apr 14 '20 19:04 fthomas