2015-04-03 5 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, потому что тогда все PositiveInt являются Int s (что на самом деле это так!), Но Int является окончательным:). Вы можете добавить неявные преобразования для всех случаев, которые вам нужны, но это будет довольно многословным.

  2. Другие объекты создаются. Может быть, можно снизить это бремя с классами стоимости (может кто-нибудь показать мне, как?).

Вот мои вопросы:

  1. я упускаю что-то? Я не видел, чтобы кто-то делал это раньше, и мне интересно, почему. Возможно, есть веские причины не делать этого.
  2. Есть ли лучший способ интегрировать валидацию в мои типы?
  3. Как я могу избежать проблем с необходимостью дублирования импликации (недостаток № 1)? Может быть, какой-то макрос, который смотрит на другие импликации в области видимости и добавляет импликации во время компиляции для меня (пример: неявное преобразование от PositiveInt до RichInt)?

ответ

-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» - я не могу просто вызвать функции, требующие «PositiveInt» с (положительными) экземплярами 'Int'. Но это то, что я хотел бы сделать ... –

+0

метод 'apply', возвращающий' Option', является неинтуитивным, если вы хотите «unapply» или 'fromInt' или что-то в этом роде. –

Смежные вопросы