Search code examples
scalatype-safety

Scala refined integer for both compile-time literal and run-time variable


I wish to limit a variable to a boolean integer representation (0 or 1), as an input to a definition. This can be implemented in two ways I've seen so far, one at runtime and one at compile time for literals only.

Is it possible to somehow combine the two, so I can create a type that will reject out of range literal values at compile-time, but will also allow for non literal inputs to be checked at runtime?

Runtime guard

Similar to this blog post: http://erikerlandson.github.io/blog/2015/08/18/lightweight-non-negative-numerics-for-better-scala-type-signatures/

/////////////////////////////
//Runtime guard for boolean
/////////////////////////////
object zero_or_one {
  import scala.language.implicitConversions

  class ZeroOrOneRuntime private (val value: Int) extends AnyVal

  object ZeroOrOneRuntime {
    def apply(v: Int) = {
      require(v == 0 || v == 1, "0 or 1 accepted only")
      new ZeroOrOneRuntime(v)
    }

    implicit def toZeroOrOneRuntime(v: Int) = ZeroOrOneRuntime(v)
  }

  implicit def toInt(nn: ZeroOrOneRuntime) = nn.value
}

import zero_or_one._

var a : ZeroOrOneRuntime = 0
val a_bad :ZeroOrOneRuntime = 2 //java.lang.IllegalArgumentException: requirement failed: 0 or 1 accepted only

for (i <- 0 to 10)
  a = i //java.lang.IllegalArgumentException: requirement failed: 0 or 1 accepted only

Compile-time guard (literals only)

By using scala refined library https://github.com/fthomas/refined

//////////////////////////////////
//Compile-time guard for boolean
//////////////////////////////////
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._

type ZeroOrOneLiteral = Int Refined Interval.Closed[W.`0`.T, W.`1`.T]

var b : ZeroOrOneLiteral = 1
val b_bad : ZeroOrOneLiteral = 2 //Right predicate of (!(2 < 0) && !(2 > 1)) failed: Predicate (2 > 1) did not fail.

for (i <- 0 to 10)
  b = i //error: compile-time refinement only works with literals

Update

After an exchange of e-mails with the creator of scala refined, this might get resolved in the library itself. I opened a feature request issue on GitHub here. I'll update this question if and when the library will be updated with this feature.


Solution

  • If anyone still follows, I have an answer. Yes, it's possible. I recently contributed to the singleton-ops library and created TwoFace and Checked types that do precisely this.

    TwoFace.XXX[T], where XXX can be Int, String, etc., is a value class that possesses both type T and run-time value. If at compile-time the type is known, then a T will possess the literal type and can be used for compile-time operations and checks. If the value is not a compile-time literal, then T will fallback to its widened type (e.g, scala.Int), and and run-time value of the class will be used instead.