typelevel / scala

Typelevel Scala, a fork of Scala
http://typelevel.org/scala/
372 stars 21 forks source link

Relaxing `ValueOf[T]` to accept non-singleton values #145

Closed soronpo closed 6 years ago

soronpo commented 7 years ago

Following a conversation started on gitter, I suggest we allow ValueOf[T] to accept non-singleton values.

Motivation We can have unified functions for both literal and non-literal of the same type (e.g, Int and Int with Singleton), and those can impose either compile-time or run-time constraints. Following is an example that will be possible to implement using singleton-ops, if this issue is resolved.

def smallerThan50[T <: Int](rw : ValueOf[T])
(implicit ct_check: CompileTime[T < 50], rt_check : RunTime[T]]) : Unit = {
  if (rt_check) require(rw.value < 50, "")
}

CompileTime[C] succeeds to return an implicit if and only if C is not a literal or C is true. RunTime[R] always returns an implicit value that can be implicitly converted to Boolean and indicates if R is not a literal type (and should be checked at run-time).

Consequently, we will be able to run the following:

implicit def toValueOf[T](t : T) : ValueOf[T] = new ValueOf[T](t)
var forty = 40
var sixty = 60

smallerThan50(40) //passes compile-time check
smallerThan50(forty) //passes run-time check
smallerThan50(60) //fails compile-time check
smallerThan50(sixty) //fails run-time check

Singleton type bounds Additionally, we may need some kind of way to signal the compiler to accept only singleton-type values. Perhaps a unified upper bound like <: AnySingleton, which is equivalent to type AnySingleton = Int with Singleton | String with Singleton | Double with Singleton | ... In dotty this can be potentially defined as-is, but in scalac the compiler would handle the unification artificially.

soronpo commented 7 years ago

On second look I think this may not be required. Bounding implicit conversion to ValueOf (or any other boxing) and creating a separate definition for every use-case, allows me to implement this with a one-time-hassle.

  implicit def toValueOfInt[T <: Int](t : T) : ValueOf[T] = new ValueOf[T](t)
  implicit def toValueOfXInt[T <: XInt](t : T) : ValueOf[T] = new ValueOf[T](t)

  type CompileTime[C] = Require[ITE[IsNotLiteral[C], true, C]]
  type RunTime[R] = SafeBoolean[IsNotLiteral[R]]

  def smallerThan50[T <: Int](rw : ValueOf[T])
                             (implicit ct_check: CompileTime[T < 50], rt_check : RunTime[T]) : Unit = {
    if (rt_check) require(rw.value < 50, "")
  }

  var forty = 40
  var sixty = 60

  smallerThan50(40) //passes compile-time check
  smallerThan50(forty) //passes run-time check
  smallerThan50(60) //fails compile-time check
  smallerThan50(sixty) //fails run-time check
scala>   smallerThan50(40) //passes compile-time check
scala>   smallerThan50(forty) //passes run-time check
scala>   smallerThan50(60) //fails compile-time check
<console>:18: error: could not find implicit value for parameter ct_check: CompileTime[60 < 50]
         smallerThan50(60) //fails compile-time check
                      ^
scala>   smallerThan50(sixty) //fails run-time check
java.lang.IllegalArgumentException: requirement failed:
  at scala.Predef$.require(Predef.scala:293)
  at .smallerThan50(<console>:20)
  ... 37 elided

@milessabin I'll leave this to you to close the issue or not. This is a good-enough alternative for my use-case.

soronpo commented 7 years ago

I'll just add my solution for this issue, as TwoFace values added for the singleton-ops library. https://contributors.scala-lang.org/t/twoface-values-closing-the-gap-between-run-compile-time-functionality/869

Again, feel free to close this issue.

milessabin commented 6 years ago

A useful discussion, but I think we should close for now and revisit later if need be.