2014-11-12 2 views
1

Я хотел бы использовать классы классов для более ясного описания типов моих данных, чтобы получить более высокую статическую корректность. Цель состоит в том, чтобы иметь 100% статическую уверенность в том, что любое существующее значение Age всегда содержит действительный возраст человека (не считая того, что правила инкапсуляции можно обойти с помощью отражения).Алгебраические типы, которые могут быть созданы только с помощью функции дозорного устройства

Например, вместо того, чтобы использовать Int хранить возраст лиц, у меня есть:

case class Age(x: Int) extends AnyVal 
def mkAge(x: Int) = if (0 <= x && x <= 150) Some(Age(x)) else None 
def unwrapAge(age: Age) = age.x 

однако, эта реализация страдает от того, что Age все еще могут быть обработаны без прохождения через mkAge и unwrapAge.

Далее я попытался сделать конструктор приватным:

case class Age private(x: Int) extends AnyVal 
object Age { 
    def make(x: Int) = if (0 <= x && x <= 150) Some(Age(x)) else None 
    def unwrap(age: Age) = age.x 
} 

однако, в то время как это делает предотвратить Age от того экземпляра, используя новый (например, new Age(3)), в object Age автоматически сгенерированный apply(x: Int) все еще легко доступен.

Итак, вот вопрос: как скрыть как конструктор, а также по умолчанию метод apply в объекте компаньона из ничего, кроме Age.make или mkAge?

Хотелось бы избежать использования обычного (не case) класса и правильно реплицировать автогенерированные методы в class Age и object Age вручную.

ответ

1

Вы были почти там:

case class Age private(private val x:Int) extends AnyVal 
object Age { 
    def mkAge(x:Int) = if(0<=x && x<=150) Some(Age(x)) else None 
    def unwrapAge(age:Age) = age.x 
} 

Обратите внимание на дополнительные private val внутри корпуса конструктора класса.

+0

Это не скомпилировано в 2.10.3: «error: value class должен иметь общедоступный параметр val». И даже если бы это было так, это не помогло бы. –

+0

@AlexeyRomanov Извините, тогда это невозможно с 2.10. Я скомпилировал его с помощью 2.11. Он достигает вашей цели: ни метод-конструктор, ни метод применения сопутствующего объекта не могут быть вызваны извне, а х также не является общедоступным. Каким образом это не помогает? – Madoc

+0

"или метод приложения сопутствующего объекта можно вызвать извне". Тогда это ошибка Scala, поскольку видимость 'Age.apply' не должна зависеть от' x'. Согласно http://www.scala-lang.org/files/archive/spec/2.11/05-classes-and-objects.html#case-classes, он всегда открыт для публики. –

1

So, here's the question: how to hide both the constructor as well as the default apply method in the companion object from anything but Age.make or mkAge?

I'd like to avoid having to use a regular (non-case) class and correctly replicate the auto-generated methods in class Age and object Age manually.

Я думал, что это было невозможно, но https://stackoverflow.com/a/25538287/9204 детализирует (а нетривиальное) решение.

+0

Есть некоторые проблемы с этим решением: A) Это очень сложно, как вы упомянули.B) Он выдает предупреждение, так как продление классов классов не рекомендуется и может быть запрещено в будущей версии Scala. C) Вы не получите льготы класса case, не переписывая их самостоятельно, что было требованием. D) Я не уверен, возможно ли иметь абстрактный класс значений, и даже если экземпляры, скорее всего, будут вставляться в коробку большую часть времени в любом случае. – Madoc

+0

@Madoc C) Какие преимущества вы теряете? Все методы все еще сгенерированы и унаследованы анонимным классом. Единственное, что вам нужно переписать, это «копировать», и я не думаю, что вы можете обойти это. D) Да, вы теряете класс ценности; если это требование, это не сработает. –

0

Я думаю, что возраст просто не должен быть классом дел. Поскольку это класс значений, вам не нужно переопределять значения equals и hashcode, также у него есть только одно поле, поэтому нет никакой выгоды от конструктора копирования. И вы ничего не можете сделать с apply() в сопутствующем объекте. Если вы все еще хотите использовать класс case, вы можете добавить require, но это не решит проблему создания экземпляра.

object A extends App { 

    import Age._ 

    println(mkAge(150)) 
    println(mkAge(151)) 
    //println(new Age(51)) //Error! 


    val a = mkAge(15) match { 
    case Some(Age(x)) => x 
    case None => 0 
    } 

    print(a) 
} 

class Age private(val x: Int) extends AnyVal { 
    override def toString = s"A($x)" 
} 

object Age { 
    def mkAge(x: Int) = if (0 <= x && x <= 150) Some(new Age(x)) else None 
    def unwrapAge(age: Age) = age.x 
    def unapply(age: Age) = if (age == null) None else Some(age.x) 
} 
+0

, если это тип значения, ему нужны равные и хэш-коды; также факт, что он имеет только одно поле, является произвольным - он может иметь 10 полей. –

+0

Это уже не будет класс значений с более чем 1 полем. Я думаю, что для классов Value также является классом case. Является ли класс ценности важным? – ka4eli

+0

Я не сказал, что AnyVal - значения типов являются общим термином. AnyVal - это просто оптимизация. –