scala类型系统的输入验证

scala类型系统的输入验证,scala,validation,types,implicit-conversion,Scala,Validation,Types,Implicit Conversion,现在我已经玩了一点Scala,我问自己应该如何在Scala中进行输入验证 这是我多次看到的: def doSomethingWithPositiveIntegers(i: Int) = { require(i>0) //do something } 要想让事情变得更清楚,感觉就像在Java中这样做: void doSomething(Object o) { if (!o instanceof Integer) throw new IllegalAr

现在我已经玩了一点Scala,我问自己应该如何在Scala中进行输入验证

这是我多次看到的:

def doSomethingWithPositiveIntegers(i: Int) = {
    require(i>0)
    //do something
}
要想让事情变得更清楚,感觉就像在Java中这样做:

void doSomething(Object o) {
    if (!o instanceof Integer)
        throw new IllegalArgumentException();
}
在那里,你首先接受比你愿意接受的更多的东西,然后引入一些只让“好的”进来的“守卫”。确切地说,在每个处理正整数的函数中都需要这些保护,如果您希望稍后包含零,则需要更改每个函数。当然,您可以将它转移到另一个函数,但是您始终需要记住调用正确的函数,并且它可能无法通过类型重构等。听起来我不希望这样。我正在考虑将此验证代码推送到数据类型本身,如下所示:

import scala.util.Try

object MyStuff {
    implicit class PositiveInt(val value: Int) {
        require(value>0)
    }
    implicit def positiveInt2Int(positiveInt: PositiveInt): Int = positiveInt.value
}

import MyStuff._

val i: MyStuff.PositiveInt = 5
val j: Int = i+5
println(i)  //Main$$anon$1$MyStuff$PositiveInt@3a16cef5
println(j)  //10
val sum = i + i
println(sum)    //10

def addOne(i: MyStuff.PositiveInt) = i + 1

println(Try(addOne(-5)))    //Failure(java.lang.IllegalArgumentException: requirement failed)
println(Try(addOne(5)))     //Success(6)
然后我有一个类型
PositiveInt
,它只能包含正整数,我可以(几乎)像
Int
一样在任何地方使用它。现在,我的API定义了我愿意接受的内容——这就是我想要的!函数本身没有什么需要验证的,因为它知道它只能得到有效的正整数——没有验证就无法构造它们。您只需在创建类型时运行一次验证!想想其他情况,验证可能更昂贵(验证电子邮件地址或URL,或者验证数字是否为素数)

优点:

  • 您的API直接告诉您接受什么类型的对象(不再是
    do(String,String,String)
    可以是
    do(用户、电子邮件、密码)
  • 您的对象“自动”得到验证
  • 编译器可以帮助您降低bug的风险。您以前在运行时看到的一些东西可以在编译时看到。例如:

    def makeNegative(i: PositiveInt): NegativeInt = -i
    addOne(makeNegative(1)) //will create a compile-time error!
    
  • 但是,也存在一些缺点:

  • 不幸的是,由于隐式转换,您破坏了许多可以工作的函数。例如,这将不起作用:

    val i: PositiveInteger = 5
    val range = i to 10        //error: value to is not a member of this.MyStuff.PositiveInt
    val range = i.value to 10  //will work
    
    如果您可以扩展
    Int
    并只添加
    require
    ,则可以解决此问题,因为所有
    PositiveInt
    都是
    Int
    s(实际情况是这样!),但
    Int
    是final:)。您可以为所有需要的情况添加隐式转换,但这将非常冗长

  • 创建了更多对象。也许可以用价值类来减轻这种负担(有人能告诉我怎么做吗?)

  • 以下是我的问题:

  • 我错过什么了吗?我以前从未见过有人这样做,我想知道为什么。也许有很好的理由不这样做
  • 是否有更好的方法将验证集成到我的类型中
  • 如何避免重复隐式的问题(缺点#1)?可能是某种宏,它在范围内查看其他隐式,并在编译时为我添加隐式(例如:从
    PositiveInt
    隐式转换为
    RichInt

  • 您可以使用工厂方法创建一个私有构造函数对伴随对象可见的类,例如

    class PositiveInt private[PositiveInt](val i: Int)
    
    object PositiveInt {
        def apply(i: Int): Option[PositiveInt] = if(i > 0) Some(new PositiveInt(i)) else None
    }
    

    客户机无法直接创建
    PositiveInt
    的实例,因此他们必须通过
    apply
    方法进行验证,并且只在输入值有效时返回有效实例。

    是的,但是-我想实现的是,用户不需要知道存在验证。在这种情况下,我需要显式实例化
    PositiveInt
    实例-我不能简单地用(positive)
    Int
    实例调用需要
    PositiveInt
    的函数。但这正是我想做的……如果您想要不应用
    fromInt
    或类似内容,则返回
    选项的
    应用
    方法是不直观的。