如何在 Scala 中为具有一个或多个值的容器实现 ADT
How to implement an ADT for a container with one or many values in Scala
归根结底,这就是我想要实现的目标:
val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
为了实现这一点,似乎需要一个代表一个或多个值的 ADT。
这是我的实现。是否有 another/better/simpler 方法来实现它(我使用了路径相关类型和 F 有界类型)。是否有已经实现它的库(用例似乎很流行)。
sealed trait OneOrMany[T <: OneOrMany[T]] {
def map(f: String => String) : T
}
final case class One(a: String) extends OneOrMany[One] {
override def map(f: String => String): One = One(f(a))
}
final case class Many(a: List[String]) extends OneOrMany[Many] {
override def map(f: String => String): Many = Many(a.map(f))
}
sealed trait Location {
type T <: OneOrMany[T]
def value: T
}
final case class OneLocation(bucket: String) extends Location {
override type T = One
override val value = One(bucket)
}
final case class ManyLocation(buckets: List[String]) extends Location {
override type T = Many
override val value = Many(buckets)
}
class Log[L <: Location](location: L, path: String) {
def getPath(): L#T = location.value.map(b => s"fs://$b/$path")
}
我不确定你是否真的需要所有这些,为什么不只是这样呢?
@annotation.implicitNotFound(msg = "${T} is not a valid Location type.")
sealed trait Location[T] {
def getPath(location: T, path: String): T
}
object Location {
final def apply[T](implicit location: Location[T]): Location[T] = location
implicit final val StringLocation: Location[String] =
new Location[String] {
override final def getPath(bucket: String, path: String): String =
s"fs://${bucket}/$path"
}
implicit final val StringListLocation: Location[List[String]] =
new Location[List[String]] {
override final def getPath(buckets: List[String], path: String): List[String] =
buckets.map(bucket => s"fs://${bucket}/$path")
}
}
final class Log[L : Location](location: L, path: String) {
def getPath(): L =
Location[L].getPath(location, path)
}
它是这样工作的:
new Log(location = "root", "foo/bar").getPath()
// val res: String = fs://root/foo/bar
new Log(location = List("base1", "base2"), "foo/bar").getPath()
// val res: List[String] = List(fs://base1/foo/bar, fs://base2/foo/bar)
new Log(location = 10, "foo/bar").getPath()
// Compile time error: Int is not a valid Location type.
如果你真的、真的、真的想要所有这些类你可以这样做:
sealed trait OneOrMany extends Product with Serializable
final case class One(path: String) extends OneOrMany
final case class Many(paths: List[String]) extends OneOrMany
sealed trait Location extends Product with Serializable {
type T <: OneOrMany
}
final case class OneLocation(bucket: String) extends Location {
override final type T = One
}
final case class ManyLocations(buckets: List[String]) extends Location {
override final type T = Many
}
@annotation.implicitNotFound(msg = "Not found a Path for Path {L}")
sealed trait Path[L <: Location] {
def getPath(location: L, path: String): L#T
}
object Path {
implicit final val OneLocationPath: Path[OneLocation] =
new Path[OneLocation] {
override final def getPath(location: OneLocation, path: String): One =
One(path = s"fs://${location.bucket}/$path")
}
implicit final val ManyLocationsPath: Path[ManyLocations] =
new Path[ManyLocations] {
override final def getPath(location: ManyLocations, path: String): Many =
Many(paths = location.buckets.map(bucket => s"fs://${bucket}/$path"))
}
}
final class Log[L <: Location](location: L, path: String) {
def getPath()(implicit ev: Path[L]): L#T =
ev.getPath(location, path)
}
如您所愿:
val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
// val onePath: One = One(fs://root/foo/bar)
val manyPath: Many = new Log(ManyLocations(List("base1", "base2")), "foo/bar").getPath()
// val manyPath: Many = Many(List(fs://base1/foo/bar, fs://base2/foo/bar)
对我来说,删除依赖路径效果很好:
sealed trait OneOrMany[T] { self: T =>
def map(f: String => String) : T
}
final case class One(a: String) extends OneOrMany[One] {
override def map(f: String => String): One = One(f(a))
}
final case class Many(a: List[String]) extends OneOrMany[Many] {
override def map(f: String => String): Many = Many(a.map(f))
}
sealed trait Location[+T] {
def value: T
}
final case class OneLocation(bucket: String) extends Location[One] {
override val value = One(bucket)
}
final case class ManyLocation(buckets: List[String]) extends Location[Many] {
override val value = Many(buckets)
}
// the only place we require OneOrMany[T]
// to provide .map(String => String): T method
class Log[T](location: Location[OneOrMany[T]], path: String) {
def getPath(): T = location.value.map(b => s"fs://$b/$path")
}
@ val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
onePath: One = One("fs://root/foo/bar")
@ val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
manyPath: Many = Many(List("fs://base1/foo/bar", "fs://base2/foo/bar"))
我会用 class (Mapper
) 类型替换 F 有界多态性。
OneOrMany
是 Location
的实现细节,但在 location.value.map...
中,我们可能稍微破坏了封装(Log
不仅知道 Location
,还知道 OneOrMany
并且它可以映射到 OneOrMany
).
我会避免类型投影 (L#T
),除非它们真的有必要(或者你有意使用它们)。
这是一种类型级的实现(尽管可能设计过度)
// libraryDependencies += "com.github.dmytromitin" %% "auxify-macros" % "0.8"
import com.github.dmytromitin.auxify.macros.{aux, instance, syntax}
import Mapper.syntax._
import LocMapper.syntax._
sealed trait OneOrMany
final case class One(a: String) extends OneOrMany
final case class Many(a: List[String]) extends OneOrMany
@syntax
trait Mapper[T <: OneOrMany] {
def map(t: T, f: String => String) : T
}
object Mapper {
implicit val one: Mapper[One] = (t, f) => One(f(t.a))
implicit val many: Mapper[Many] = (t, f) => Many(t.a.map(f))
}
@aux
sealed trait Location {
protected type T <: OneOrMany
val value: T
}
final case class OneLocation(bucket: String) extends Location {
override type T = One
override val value = One(bucket)
}
final case class ManyLocation(buckets: List[String]) extends Location {
override type T = Many
override val value = Many(buckets)
}
@aux @instance
trait ToLocation[T <: OneOrMany] {
type Out <: Location.Aux[T]
def apply(t: T): Out
}
object ToLocation {
implicit val one: Aux[One, OneLocation] = instance(t => OneLocation(t.a))
implicit val many: Aux[Many, ManyLocation] = instance(t => ManyLocation(t.a))
}
@syntax
trait LocMapper[L <: Location] {
def map(l: L, f: String => String): L
}
object LocMapper {
implicit def mkLocMapper[T <: OneOrMany, L <: Location.Aux[T]](implicit
ev: L <:< Location.Aux[T],
m: Mapper[T],
toLoc: ToLocation.Aux[T, L]
): LocMapper[L] = (l, f) => toLoc(l.value.map(f))
}
class Log[L <: Location : LocMapper](location: L, path: String) {
def getPath(): L = location.map(b => s"fs://$b/$path")
}
val onePath: OneLocation = new Log(OneLocation("root"), "foo/bar").getPath()
// OneLocation(fs://root/foo/bar)
val manyPath: ManyLocation = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
// ManyLocation(List(fs://base1/foo/bar, fs://base2/foo/bar))
归根结底,这就是我想要实现的目标:
val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
为了实现这一点,似乎需要一个代表一个或多个值的 ADT。
这是我的实现。是否有 another/better/simpler 方法来实现它(我使用了路径相关类型和 F 有界类型)。是否有已经实现它的库(用例似乎很流行)。
sealed trait OneOrMany[T <: OneOrMany[T]] {
def map(f: String => String) : T
}
final case class One(a: String) extends OneOrMany[One] {
override def map(f: String => String): One = One(f(a))
}
final case class Many(a: List[String]) extends OneOrMany[Many] {
override def map(f: String => String): Many = Many(a.map(f))
}
sealed trait Location {
type T <: OneOrMany[T]
def value: T
}
final case class OneLocation(bucket: String) extends Location {
override type T = One
override val value = One(bucket)
}
final case class ManyLocation(buckets: List[String]) extends Location {
override type T = Many
override val value = Many(buckets)
}
class Log[L <: Location](location: L, path: String) {
def getPath(): L#T = location.value.map(b => s"fs://$b/$path")
}
我不确定你是否真的需要所有这些,为什么不只是这样呢?
@annotation.implicitNotFound(msg = "${T} is not a valid Location type.")
sealed trait Location[T] {
def getPath(location: T, path: String): T
}
object Location {
final def apply[T](implicit location: Location[T]): Location[T] = location
implicit final val StringLocation: Location[String] =
new Location[String] {
override final def getPath(bucket: String, path: String): String =
s"fs://${bucket}/$path"
}
implicit final val StringListLocation: Location[List[String]] =
new Location[List[String]] {
override final def getPath(buckets: List[String], path: String): List[String] =
buckets.map(bucket => s"fs://${bucket}/$path")
}
}
final class Log[L : Location](location: L, path: String) {
def getPath(): L =
Location[L].getPath(location, path)
}
它是这样工作的:
new Log(location = "root", "foo/bar").getPath()
// val res: String = fs://root/foo/bar
new Log(location = List("base1", "base2"), "foo/bar").getPath()
// val res: List[String] = List(fs://base1/foo/bar, fs://base2/foo/bar)
new Log(location = 10, "foo/bar").getPath()
// Compile time error: Int is not a valid Location type.
如果你真的、真的、真的想要所有这些类你可以这样做:
sealed trait OneOrMany extends Product with Serializable
final case class One(path: String) extends OneOrMany
final case class Many(paths: List[String]) extends OneOrMany
sealed trait Location extends Product with Serializable {
type T <: OneOrMany
}
final case class OneLocation(bucket: String) extends Location {
override final type T = One
}
final case class ManyLocations(buckets: List[String]) extends Location {
override final type T = Many
}
@annotation.implicitNotFound(msg = "Not found a Path for Path {L}")
sealed trait Path[L <: Location] {
def getPath(location: L, path: String): L#T
}
object Path {
implicit final val OneLocationPath: Path[OneLocation] =
new Path[OneLocation] {
override final def getPath(location: OneLocation, path: String): One =
One(path = s"fs://${location.bucket}/$path")
}
implicit final val ManyLocationsPath: Path[ManyLocations] =
new Path[ManyLocations] {
override final def getPath(location: ManyLocations, path: String): Many =
Many(paths = location.buckets.map(bucket => s"fs://${bucket}/$path"))
}
}
final class Log[L <: Location](location: L, path: String) {
def getPath()(implicit ev: Path[L]): L#T =
ev.getPath(location, path)
}
如您所愿:
val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
// val onePath: One = One(fs://root/foo/bar)
val manyPath: Many = new Log(ManyLocations(List("base1", "base2")), "foo/bar").getPath()
// val manyPath: Many = Many(List(fs://base1/foo/bar, fs://base2/foo/bar)
对我来说,删除依赖路径效果很好:
sealed trait OneOrMany[T] { self: T =>
def map(f: String => String) : T
}
final case class One(a: String) extends OneOrMany[One] {
override def map(f: String => String): One = One(f(a))
}
final case class Many(a: List[String]) extends OneOrMany[Many] {
override def map(f: String => String): Many = Many(a.map(f))
}
sealed trait Location[+T] {
def value: T
}
final case class OneLocation(bucket: String) extends Location[One] {
override val value = One(bucket)
}
final case class ManyLocation(buckets: List[String]) extends Location[Many] {
override val value = Many(buckets)
}
// the only place we require OneOrMany[T]
// to provide .map(String => String): T method
class Log[T](location: Location[OneOrMany[T]], path: String) {
def getPath(): T = location.value.map(b => s"fs://$b/$path")
}
@ val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
onePath: One = One("fs://root/foo/bar")
@ val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
manyPath: Many = Many(List("fs://base1/foo/bar", "fs://base2/foo/bar"))
我会用 class (Mapper
) 类型替换 F 有界多态性。
OneOrMany
是 Location
的实现细节,但在 location.value.map...
中,我们可能稍微破坏了封装(Log
不仅知道 Location
,还知道 OneOrMany
并且它可以映射到 OneOrMany
).
我会避免类型投影 (L#T
),除非它们真的有必要(或者你有意使用它们)。
这是一种类型级的实现(尽管可能设计过度)
// libraryDependencies += "com.github.dmytromitin" %% "auxify-macros" % "0.8"
import com.github.dmytromitin.auxify.macros.{aux, instance, syntax}
import Mapper.syntax._
import LocMapper.syntax._
sealed trait OneOrMany
final case class One(a: String) extends OneOrMany
final case class Many(a: List[String]) extends OneOrMany
@syntax
trait Mapper[T <: OneOrMany] {
def map(t: T, f: String => String) : T
}
object Mapper {
implicit val one: Mapper[One] = (t, f) => One(f(t.a))
implicit val many: Mapper[Many] = (t, f) => Many(t.a.map(f))
}
@aux
sealed trait Location {
protected type T <: OneOrMany
val value: T
}
final case class OneLocation(bucket: String) extends Location {
override type T = One
override val value = One(bucket)
}
final case class ManyLocation(buckets: List[String]) extends Location {
override type T = Many
override val value = Many(buckets)
}
@aux @instance
trait ToLocation[T <: OneOrMany] {
type Out <: Location.Aux[T]
def apply(t: T): Out
}
object ToLocation {
implicit val one: Aux[One, OneLocation] = instance(t => OneLocation(t.a))
implicit val many: Aux[Many, ManyLocation] = instance(t => ManyLocation(t.a))
}
@syntax
trait LocMapper[L <: Location] {
def map(l: L, f: String => String): L
}
object LocMapper {
implicit def mkLocMapper[T <: OneOrMany, L <: Location.Aux[T]](implicit
ev: L <:< Location.Aux[T],
m: Mapper[T],
toLoc: ToLocation.Aux[T, L]
): LocMapper[L] = (l, f) => toLoc(l.value.map(f))
}
class Log[L <: Location : LocMapper](location: L, path: String) {
def getPath(): L = location.map(b => s"fs://$b/$path")
}
val onePath: OneLocation = new Log(OneLocation("root"), "foo/bar").getPath()
// OneLocation(fs://root/foo/bar)
val manyPath: ManyLocation = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
// ManyLocation(List(fs://base1/foo/bar, fs://base2/foo/bar))