如何在 Scala 中为具有一个或多个值的容器实现 ADT

扬·莫伊桑

在一天结束时,这是我想要实现的目标:

  val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
  val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()

为了实现这一点,似乎需要一个代表一个或多个值的 ADT。

这是我的实现。是否有另一种/更好/更简单的方法来实现它(我使用了路径依赖类型和 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)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章