保证可选字段存在时的类型安全

Type safety when optional field is guaranteed to be present

假设我有以下情况class:

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

这里可以看到categoryId是可选的

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

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

你看,此方法 returns 产品 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_!,但它需要您创建两个 case classes 和一个重复所有字段的特征,这也很糟糕。

我的问题:如何使用 Scala 类型系统来声明这种特定情况而不会重复这些字段?

不确定这将针对您的特定情况扩展多少,但想到的一种解决方案是使用更高级的通用类型对 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]] = ???
}

一种方法是使用类型 类 -

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]
}

此解决方案涉及一些代码并依赖于隐式,但可以实现您想要实现的目标。 请注意,此解决方案并非完全密封,可以'hacked'。你可以作弊并做一些类似 -

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

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