【问题标题】:Scala type for limiting an Int to a range用于将 Int 限制在范围内的 Scala 类型
【发布时间】:2020-05-12 22:26:44
【问题描述】:

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

有没有办法定义一个允许这样做的类型?

一个如何使用它的例子:

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

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

我也想保留 GoodX 的“完整性”。

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

【问题讨论】:

    标签: scala


    【解决方案1】:

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

    【讨论】:

    【解决方案2】:

    我认为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)
    

    此解决方案不需要库。 希望对您有所帮助。

    【讨论】:

      【解决方案3】:

      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 上,也可以使用如下所示的基于字面量的单例类型,不需要来自 shapeless 的 Witness ;)

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

      详情请参阅SIPofficial documentation

      【讨论】:

        【解决方案4】:

        当然……

        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)
        

        但是,很明显,这不会让你的类型比它已经是更“int-ful”(几乎没有),因为像 val v: Value = _15 - _10val v: Value = _5 * 3 甚至 val v = _15 * _5 都可以,但其他的,比如 val v: Value = _5 - 1 会崩溃

        【讨论】:

        • 我考虑过枚举,但我认为它不能保留范围的“完整性”。
        • 基数类型没有什么“int-ful”,它只定义了 11 个不同的值。
        猜你喜欢
        • 1970-01-01
        • 2015-11-21
        • 2022-12-14
        • 2014-11-07
        • 1970-01-01
        • 2017-11-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多