用于将Int限制为范围的Scala类型

时间:2016-05-27 15:23:13

标签: scala

有一个程序,我希望将一组整数的范围限制在5到15之间。

有没有办法定义允许这种情况的类型?

如何使用此示例:

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

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

我还想保留" Int-iness" of GoodX。

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

4 个答案:

答案 0 :(得分:6)

看看https://github.com/fthomas/refined。它允许您在类型级别细化(约束)现有类型。例如。正整数,它仍然与整数有一个子类型关系。

语法有点冗长,它将填充原语(详见下文)。但除此之外,它完全符合您的要求。

这是一个简短的演示。使用精炼类型定义细化和方法:

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

将它与常量一起使用(注意好的编译错误消息):

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)
             ^

如果有变量,则在编译时不知道它们是否在范围内。因此,从Int到精简int的向下转换可能会失败。抛出异常在功能库中不被认为是好的风格。因此refineV方法返回一个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)

答案 1 :(得分:0)

我认为Partial Function会有所帮助。

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)

此解决方案不需要库。 希望这有帮助。

答案 2 :(得分:0)

examples section of refined library所示,我们可以定义一个自定义的精简类型,其值在7到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]

此外,如果在 scala 2.13.x 上,也可以使用基于文字的单例类型,如下所示,因为不需要来自无形的见证;)

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

有关更多详细信息,请参阅SIPofficial documentation

答案 3 :(得分:-1)

当然......

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

修改如果您想“保留内容”,您还可以添加如下转换:

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

但是,显然,这不会使你的类型更加“完整”然后它已经是(几乎没有),因为像 val v: Value = _15 - _10val v: Value = _5 * 3甚至val v = _15 * _5都可以使用,但val v: Value = _5 - 1等其他人会崩溃