Home > database >  scala even type number
scala even type number

Time:07-08

The only way I can think of doing this, without creating a wrapper class, is to use scala 3's type unions like this

type Even = 0 | 2 | 4 | 6 | 8
val even : Even = 4

but that obviously has a limit. Is there a way to create the "entire" range?

As a follow up, what about for other ranges? Is there some way to create a function that restricts the type in some arbitrary way (as dangerous as that sounds)?

CodePudding user response:

You can create a newtype with a smart constructor. Several ways to do it.

First, manually, to show how it work:

trait Newtype[T] {

  type Type

  protected def wrap(t: T): Type = t.asInstanceOf[Type]
  protected def unwrap(t: Type): T = t.asInstanceOf[T]
}

type Even = Even.Type
object Even extends Newtype[Int] {

  def parse(i: Int): Either[String, Even] =
   if (i % 2 == 0) Right(wrap(i))
   else Left(s"$i is odd")
  
  implicit class EvenOps(private val even: Even) extends AnyVal {
    
    def value: Int = unwrap(even)

    def  (other: Even): Even = wrap(even.value   other.value)
    def -(other: Even): Even = wrap(even.value - other.value)
  }
}

You are creating type Even which compiler knows nothing about, so it cannot prove that an arbitrary value is its instance. But you can force-cast to it an back again - if JVM in runtime won't be able to catch some issue with it, there is not problem (and since it assumes nothing about Even it cannot disprove anything by contradiction).

Since Even resolves to Even.Type - that is type Type within Even object - Scala's implicit scope will automatically fetch all implicits that are defined in object Even, so you can place your extension methods and typeclasses there.

This will help you pretend that this type has some methods defined.

In Scala 3 you can achieve the same with opaque type. However this representation, has the nice side that it is easy to make it cross compilable with Scala 2 and Scala 3. As a matter of the fast, that's what Monix Newtype did, so you can use it instead of implementing this functionality yourself.

import monix.newtypes._

type Even = Even.Type
object Even extends Newtype[Int] {

  // ...
}

Another option is older macro-annotation based library Scala Newtype. It will take your type defined as case class and rewrite the code to implement something similar to what we have above:

import io.estatico.newtype.macros.newtype

@newtype case class Even(value: Int)

however it is harder to add your own smart constructor there, which is why it usually is paired with Refined Types. Then your code would look like:

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric
import io.estatico.newtype.macros.newtype

@newtype case class Even(value: Int Refined numeric.Even)
object Even {

  def parse(i: Int): Either[String, Even] =
    refineV[numeric.Even](i).map(Even(_))
}

However, you might want to just use the plain refined type at this point, since Even newtype wouldn't introduce any domain knowledge beyond what refinement does.

  • Related