可以通过sentinel函数实例化的代数类型

时间:2014-11-12 01:12:22

标签: scala static-typing

我想使用案例类来更明确地描述我的数据类型,以便从更高的静态正确性中受益。目标是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仍然可以在不经过mkAgeunwrapAge的情况下进行实例化。

接下来,我尝试将构造函数设为私有:

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(例如new Age(3))进行实例化,但apply(x: Int)中自动生成的object Age仍然是容易到达。

所以,问题是:如何隐藏配对对象中的构造函数和默认apply方法,除了Age.makemkAge之外的其他任何内容?

我希望避免使用常规(非case)类并正确地在class Ageobject Age中手动复制自动生成的方法。

3 个答案:

答案 0 :(得分: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
}

注意case类构造函数中的额外private val

答案 1 :(得分:1)

  

所以,这里有一个问题:除了Age.make或mkAge之外,如何隐藏伴随对象中的构造函数和默认apply方法?

     

我想避免使用常规(非案例)类并正确地手动复制Age类和对象Age中的自动生成方法。

我认为这是不可能的,但https://stackoverflow.com/a/25538287/9204详述了一个(相当不重要的)解决方案。

答案 2 :(得分:0)

我认为Age不需要成为案例类。因为它是一个Value Class,所以你不需要重写equals和hashcode,它也只有一个字段,所以复制构造函数没有任何好处。你无法在伴侣对象中使用 apply()做任何事情。如果您仍想使用案例类,可以添加 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)
}