如何为Scala中具有一个或多个值的容器实现ADT
最后,我想实现以下目标:如何为Scala中具有一个或多个值的容器实现ADT,scala,algebraic-data-types,path-dependent-type,f-bounded-polymorphism,Scala,Algebraic Data Types,Path Dependent Type,F Bounded Polymorphism,最后,我想实现以下目标: val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath() val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath() 为了实现这一点,似乎需要一个表示一个或多个值的AD
val onePath: One = new Log(OneLocation("root"), "foo/bar").getPath()
val manyPath: Many = new Log(ManyLocation(List("base1", "base2")), "foo/bar").getPath()
为了实现这一点,似乎需要一个表示一个或多个值的ADT
这是我的实现。是否有另一种/更好/更简单的方法来实现它(我使用了路径依赖类型和F-有界类型)。是否有一个已经实现了它的库(用例似乎非常流行)
oneormy[T字符串):T
}
最后一个案例类1(a:String)扩展了一个或多个[1]{
覆盖def映射(f:String=>String):一个=一个(f(a))
}
最后一个case类Many(a:List[String])扩展了一个或多个{
覆盖defmap(f:String=>String):Many=Many(a.map(f))
}
封闭性状定位{
输入T我不确定你是否真的需要所有这些,为什么不这样做呢
@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)
}
对我来说,删除依赖路径效果很好:
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")
}
我将用一个类型类(Mapper
)替换F-有界多态性
OneOrMany
是Location
的实现细节,但在Location.value.map…
中,也许我们稍微打破了封装(Log
不仅知道Location
,还知道OneOrMany
,它可以映射到OneOrMany
)
我会避免类型投影(L#T
),除非它们确实是必要的(或者您有意使用它们)
这是一种类型级实现(尽管可能设计过度)
//libraryDependencies+=“com.github.dmytromitin”%%“auxify宏”%%“0.8”
导入com.github.dmytromitin.auxify.macros.{aux,instance,syntax}
导入Mapper.syntax_
导入LocMapper.syntax_
密封特性一个或多个
最后一个案例类1(a:String)扩展了一个或多个
最后一个case类Many(a:List[String])扩展了一个或多个
@语法
特征映射器[T字符串):T
}
对象映射器{
隐式valone:Mapper[one]=(t,f)=>one(f(t.a))
隐式valmany:Mapper[many]=(t,f)=>many(t.a.map(f))
}
@辅助
封闭性状定位{
受保护类型T非常感谢您提出了另一种方法。非常有趣。我发现意图不太清楚,因为它被typeclass机制的技术代码稀释了。@Yannomisan你是什么意思?为什么意图不那么清楚?你想要哪种意图?另外,你可以在现有方法中添加一个typeclass。我只是认为你正在创建许多不必要的样板类,但是如果你真的需要它们,那么混合应该很简单。我认为更容易看到有两种情况是Location
和Location
ADT。@Yannomisan你可以拥有Location
ADT加上Path
typeclass,我会编辑我的答案。在t之后仔细想想,我同意。最好不要向getPath方法的客户端公开OneOrMany
(这是一个实现细节)。感谢您指出我在这里不需要抽象类型。当我想要返回给定上下文的不同类型时,我使用它的反射不好。
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"))
// 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))