当保证可选字段存在时键入安全性

时间:2015-04-07 09:05:37

标签: scala design-patterns types

假设我有以下案例类:

case class Product(name: String, categoryId: Option[Long]/*, other fields....*/)

您可以在此处看到categoryId是可选的。

现在假设我的DAO层中有以下方法:

getCategoryProducts(): List[Product] = {
    // query products that have categoryId defined
}

您会看到,此方法会返回guaranteed的产品,以使categoryId定义为某个值。

我想做的是这样的事情:

trait HasCategory {
    def categoryId_!: Long
}
// and then specify in method signature
getCategoryProducts(): List[Product with HasCategory]

这样可行,但是这样的产品会有两种方法:categoryId_!categoryId闻起来很糟糕。

另一种方式是:

sealed trait Product {
    def name: String
    /*other fields*/
}
case class SimpleProduct(name: String, /*, other fields....*/) extends Product 
case class ProductWithCategory(name: String, categoryId: Long/*, other fields....*/) extends Product 
def getCategoryProducts: List[ProductWithCategory] = ...

此方法有助于避免重复方法categoryId和categoryId_ !,但它要求您创建两个案例类和一个重复所有字段的特征,这些字体也有气味。

我的问题:如何在没有这些字段重复的情况下使用Scala类型系统声明此特定情况?

2 个答案:

答案 0 :(得分:2)

不确定这会针对您的具体情况进行多大扩展,但我想到的一个解决方案是使用更高级别的通用类型对Option类型进行参数化:

object Example {
  import scala.language.higherKinds

  type Id[A] = A

  case class Product[C[_]](name: String, category: C[Long])

  def productsWithoutCategories: List[Product[Option]] = ???

  def productsWithCategories: List[Product[Id]] = ???
}

答案 1 :(得分:1)

一种方法是使用类型类 -

import scala.language.implicitConversions

object Example {

  sealed class CartId[T]

  implicit object CartIdSomeWitness extends CartId[Some[Long]]
  implicit object CartIdNoneWitness extends CartId[None.type]
  implicit object CartIdPresentWitness extends CartId[Long]

  case class Product[T: CartId](name: String, categoryId: T /*, other fields....*/)

  val id: Long = 7

  val withId = Product("dsds", id)
  val withSomeId = Product("dsds", Some(id))
  val withNoneId = Product("dsds", None)

  val presentId: Long = withId.categoryId
  val maybeId: Some[Long] = withSomeId.categoryId
  val noneId: None.type = withNoneId.categoryId

  val p = Product("sasa", true) //Error:(30, 18) could not find implicit value for evidence parameter of type com.novak.Program.CartId[Boolean]
}

此解决方案涉及一些代码并依赖于implicits,但会执行您尝试实现的内容。 请注意,此解决方案并未完全密封,并且可以被黑客入侵。你可以欺骗并做一些像 -

  val hack: Product[Boolean] = Product("a", true)(new CartId[Boolean])
  val b: Boolean =hack.categoryId

对于一些更高级的解决方案,包括 * Miles Sabin(@milessabin)通过Curry-Howard同构在Scala中的Unboxed联合类型 * Scalaz /运营商 http://eed3si9n.com/learning-scalaz/Coproducts.html