Reputation: 443
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
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
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
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
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