2

I am new to scala and the refined library but I am trying to create two refined types based on UUIDs.

In order to do so, I did this (Note: Uuid in this case comes from eu.timepit.refined.string.Uuid):

type UuidPredicate = Uuid

type UuidA = String Refined UuidPredicate
type UuidB = String Refined UuidPredicate

However, it appears as though this only creates aliases, and therefore there is no type safety.

So if I had a constructor like Product(a UuidA, b UuidB) and proceeded to do something like this:

val myUuid: UuidA = "9f9ef0c6-b6f8-11ea-b3de-0242ac130004"
val Product = Product(myUuid, myUuid)

It would compile and run properly. Is there anyway to ensure this is not the case? If a variable is created as one type, how can I make it so it only be used as that particular refined type, even though the types are fundamentally the same?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
stigward
  • 127
  • 13
  • You want a [**newtype**](https://github.com/estatico/scala-newtype) not sure how well both libraries play together. BTW, quick disclaimer all this is pretty advanced stuff, if you are new to the language you may suffer a bit. – Luis Miguel Mejía Suárez Jun 25 '20 at 15:38
  • 1
    @LuisMiguelMejíaSuárez `newtype` introduces macro annotation (expanded earlier, case class is replaced with a type and object) and `refined` introduces implicit def macros (expanded later, conversions `String => String Refined ...`) so there shouldn't be problems. – Dmytro Mitin Jun 25 '20 at 18:18

1 Answers1

2

The simplest is to introduce different data types

case class UuidA(value: String Refined UuidPredicate)
case class UuidB(value: String Refined UuidPredicate)

You can't make UuidA, UuidB extend AnyVal because Refined already extends AnyVal and Scala doesn't allow nested value classes.

If you prefer to avoid runtime overhead of wrapping with UuidA, UuidB you can try @newtype as @LuisMiguelMejíaSuárez adviced

import io.estatico.newtype.macros.newtype

@newtype case class UuidA(value: String Refined UuidPredicate)
@newtype case class UuidB(value: String Refined UuidPredicate)

Or try to add more tags

import eu.timepit.refined.api.Refined
import eu.timepit.refined.string.Uuid
import eu.timepit.refined.auto._
import shapeless.tag
import shapeless.tag.@@

type UuidPredicate = Uuid
type UuidString = Refined[String, UuidPredicate]
type TagA
type TagB
type UuidA = UuidString @@ TagA
type UuidB = UuidString @@ TagB

case class Product(a: UuidA, b: UuidB)
val myUuid: UuidA = tag[TagA][UuidString]("9f9ef0c6-b6f8-11ea-b3de-0242ac130004")
//  val product = Product(myUuid, myUuid) // doesn't compile
val myUuid1: UuidB = tag[TagB][UuidString]("9f9ef0c6-b6f8-11ea-b3de-0242ac130004")
val product1 = Product(myUuid, myUuid1) // compiles
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66