Is it possible to restrict Int by creating something like PositiveInt and have compile-time checks in Scala?

后端 未结 4 1171
失恋的感觉
失恋的感觉 2021-01-07 18:16

Is it possible to create a restricted Int such as PositiveInt and have compile-time checks for it? In other words is it possible to define a method such as:

相关标签:
4条回答
  • 2021-01-07 18:46

    If you have compile time dependent type checking, you solved the halting problem or your compiler is not always guaranteed to finish compiling or the grammer of your programming language needs to be Turing complete all of which are ridiculous things.

    Here is a reasonable alternative for dependent typing during runtime

    object ScalaDependentTyping extends App {
    
      implicit class NaturalNumber(val x: Int) {
        assume(x >= 0)
      }
    
      implicit def toInt(y: NaturalNumber): Int = y.x
    
      def addOne(n: NaturalNumber) = n+1
    
      println(addOne(0))
      println(addOne(1))
      println(addOne(2))
    
      println(addOne(-1))  //exception
    }
    
    0 讨论(0)
  • 2021-01-07 18:57

    This kind of thing is called dependent typing, and no, it's not available in Scala.

    0 讨论(0)
  • 2021-01-07 18:58

    The other answers refer mostly to the issue of creating a "positive number" type that relates to a "number" type in a way the compiler can guarantee correctness. For example with dependent types you can proof that the implementation of functions like abs are correct. You can't do that with scala.

    You can, however, build a type that represents positive integers and nothing else. For example:

    abstract class Pos {
      def toInt: Int
    }
    
    case object Zero extends Pos {
      def toInt: Int = 0
    }
    
    case class Next(x: Pos) extends Pos {
      def toInt: Int = 1 + x.toInt
    }
    
    object Pos {
      def apply(x: Int): Pos =
        x match {
          case n if (n < 0) => throw new IllegalArgumentException(s"$x is not a positive integer")
          case 0 => Zero
          case n => Next(Pos(n-1))
        }
    }
    

    This is similar to what ziggystar proposed, with the difference that is the correctness of the type is guaranteed by its own construction, not by the constructor. That is, it is impossible to represent a negative number with this type.

    This approach is likely not to be practical for your purposes. You either need to implement all the operations for it or piggyback to the Int value, which then is equivalent to just have a runtime check since you lose all type safety you won by representing positive integers in this way.

    That is essentially what would happen in your example. Since Pos.apply is not type safe, you cannot get a compile error in

    myMethod(-5)
    
    0 讨论(0)
  • 2021-01-07 19:10

    You can use a marker trait on primitve types in the following way

    trait Positive
    type PosInt = Int with Positive
    def makePositive(i: Int): Option[PosInt] = if(i < 0) None else Some(i.asInstanceOf[PosInt])
    def succ(i: PosInt): PosInt = (i + 1).asInstanceOf[PosInt]
    

    But you will only get a runtime error for writing makePositive(-5). You will get a compile time error for writing succ(5).

    It is probably possible to write a compiler plugin that "lifts" positive integer literals to a marked type.

    Edit

    I have not tested whether there is any runtime overhead for marking primitive types in such a way.

    0 讨论(0)
提交回复
热议问题