现在,我已经在Scala中玩过一段了,我问自己如何在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) //[email protected]
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(User, Email, Password)
) - 你的对象得到验证“自动”
编译器可以帮助你减少了错误的风险。在编译时可以看到在运行时看到的一些事情。例如:
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
是最后的: )。您可以为所有需要的情况添加隐式转换,但这会非常冗长。创建更多对象。也许人们可以用价值等级降低这种负担(任何人都可以告诉我怎么做?)。
这是我的问题:
- 我缺少的东西?我以前从来没有见过这样的人,我想知道为什么。也许有没有这样做的很好理由。
- 有没有更好的方法来将验证整合到我的类型中?
- 我该如何避免需要重复隐含(缺点#1)的问题?也许某种类型的宏查看范围中的其他含义并在编译时为我添加含义(例如:从
PositiveInt
到RichInt
的隐式转换)?
是的,但是 - 我想要实现的是用户不需要意识到存在验证。在这种情况下,我需要显式实例化'PositiveInt'实例 - 我不能简单地调用需要带有(Positive)Int实例的'PositiveInt'的函数。但这就是我想要做的...... – 2015-04-23 20:49:18
如果你想要一个'unapply'或'fromInt'或类似的东西,一个'apply'方法返回一个'Option'是不直观的。 – 2017-03-01 12:35:42