保证可选字段存在时的类型安全
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
假设我有以下情况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