TLOlczyk
TLOlczyk

Reputation: 443

Scala type for limiting an Int to a range

There is a program where I would like to limit the range on a set of ints from 5 to 15.

Is there a way to define a type which allows this?

An example of how would like to use this:

// Define type Good X as range from 5 to 15

class Foo(val x: GoodX) 
{ 
   //blah blah 
}

I would also like to preserve the "Int-iness" of GoodX.

val base:GoodX=5
val f=Foo(base+4)

Upvotes: 5

Views: 1935

Answers (4)

Viswanath
Viswanath

Reputation: 1551

As shown in examples section of refined library we can define a custom refined type whose value is between 7 and 77

// Here we define a refined type "Int with the predicate (7 <= value < 77)".
scala> type Age = Int Refined Interval.ClosedOpen[W.`7`.T, W.`77`.T]

Furthermore, if on scala 2.13.x, one can also use literal based singleton types as shown below there by not needing Witness from shapeless ;)

import eu.timepit.refined.numeric.Interval.Closed
type AgeOfChild = Int Refined Closed[2, 12]
case class Child(name: NonEmptyString, age:AgeOfChild = 2)

Please refer to SIP and official documentation for more details.

Upvotes: 2

Qingwei
Qingwei

Reputation: 510

I think Partial Function would help.

case class GoodX(x: Int)

object GoodX {
  def apply: PartialFunction[Int, GoodX] = 
    { case i if i > 5 && i < 15 => new GoodX(i) }
}

// implicits to remain int-fulness
implicit def goodXToInt(goodX: GoodX): Int = goodX.x

GoodX(5)   // throw Match Error
GoodX(10)  // GoodX(10)

This solution requires no library. Hope this help.

Upvotes: 2

R&#252;diger Klaehn
R&#252;diger Klaehn

Reputation: 12565

Take a look at https://github.com/fthomas/refined . It allows you to refine (constrain) existing types at type level. E.g. positive integers, which still have a subtype relationship with integers.

The syntax is a bit verbose, and it will box primitives (see below for details). But other than that it does exactly what you want.

Here is a short demo. Define a refinement and a method using a refined type:

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._

type FiveToFifteen = GreaterEqual[W.`5`.T] And Less[W.`15`.T]
type IntFiveToFifteen = Int Refined FiveToFifteen

def sum(a: IntFiveToFifteen, b: IntFiveToFifteen): Int = a + b

Use it with constants (note the good compile error messages):

scala> sum(5,5)
res6: Int = 10

scala> sum(0,10)
<console>:60: error: Left predicate of (!(0 < 5) && (0 < 15)) failed: Predicate (0 < 5) did not fail.
       sum(0,10)
           ^

scala> sum(5,20)
<console>:60: error: Right predicate of (!(20 < 5) && (20 < 15)) failed: Predicate failed: (20 < 15).
       sum(5,20)
             ^

When you have variables, you do not know at compile time whether they are in range or not. So downcasting from Int to a refined int can fail. Throwing exceptions is not considered good style in functional libraries. So the refineV method returns an Either:

val x = 20
val y = 5

scala> refineV[FiveToFifteen](x)
res14: Either[String,eu.timepit.refined.api.Refined[Int,FiveToFifteen]] = Left(Right predicate of (!(20 < 5) && (20 < 15)) failed: Predicate failed: (20 < 15).)

scala> refineV[FiveToFifteen](y)
res16: Either[String,eu.timepit.refined.api.Refined[Int,FiveToFifteen]] = Right(5)

Upvotes: 8

Dima
Dima

Reputation: 40500

Sure ...

object FiveToFifteen extends Enumeration {
 val _5 = Value(5)
 val _6,_7,_8,_9,_10,_11,_12,_13,_14,_15 = Value
}

Edit if you want to "preserve int-ness", you could also add conversions like this:

 implicit def toInt(v: Value) = v.id
 implicit def fromInt(i: Int) = apply(i)

But this, obviously, won't make your type much more "int-ful" then it already is (which is, pretty much none), because things like val v: Value = _15 - _10 or val v: Value = _5 * 3 or even val v = _15 * _5 will work, but others, like val v: Value = _5 - 1 will crash

Upvotes: 0

Related Questions