2015-04-03 72 views
3

现在,我已经在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,或者数字是主要的)。

优点:

  1. 你的API告诉你直接你接受什么样的对象(不超过do(String, String, String)这可能是do(User, Email, Password)
  2. 你的对象得到验证“自动”
  3. 编译器可以帮助你减少了错误的风险。在编译时可以看到在运行时看到的一些事情。例如:

    def makeNegative(i: PositiveInt): NegativeInt = -i 
    addOne(makeNegative(1)) //will create a compile-time error! 
    

不过,也有一些缺点:

  1. 不幸的是,你打破许多工作,由于隐式转换功能。例如,这是不行的:

    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,因为那时所有PositiveIntInt S(什么果然是这样!),但Int是最后的: )。您可以为所有需要的情况添加隐式转换,但这会非常冗长。

  2. 创建更多对象。也许人们可以用价值等级降低这种负担(任何人都可以告诉我怎么做?)。

这是我的问题:

  1. 我缺少的东西?我以前从来没有见过这样的人,我想知道为什么。也许有没有这样做的很好理由。
  2. 有没有更好的方法来将验证整合到我的类型中?
  3. 我该如何避免需要重复隐含(缺点#1)的问题?也许某种类型的宏查看范围中的其他含义并在编译时为我添加含义(例如:从PositiveIntRichInt的隐式转换)?

回答

-1

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

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方法,做验证,如果输入的数值仅返回有效的实例。

+0

是的,但是 - 我想要实现的是用户不需要意识到存在验证。在这种情况下,我需要显式实例化'PositiveInt'实例 - 我不能简单地调用需要带有(Positive)Int实例的'PositiveInt'的函数。但这就是我想要做的...... – 2015-04-23 20:49:18

+0

如果你想要一个'unapply'或'fromInt'或类似的东西,一个'apply'方法返回一个'Option'是不直观的。 – 2017-03-01 12:35:42