Scala 隐式转换不使用类型安全生成器模式
我将Scala类型安全构建器模式用于一个简单的rest请求。作为一个流畅的api,这非常有用Scala 隐式转换不使用类型安全生成器模式,scala,dsl,type-inference,implicit-conversion,Scala,Dsl,Type Inference,Implicit Conversion,我将Scala类型安全构建器模式用于一个简单的rest请求。作为一个流畅的api,这非常有用 sealed abstract class Method(name: String) case object GET extends Method("GET") case object POST extends Method("POST") abstract class TRUE abstract class FALSE case class Builder[HasMethod, HasUri](
sealed abstract class Method(name: String)
case object GET extends Method("GET")
case object POST extends Method("POST")
abstract class TRUE
abstract class FALSE
case class Builder[HasMethod, HasUri](
method: Option[Method],
uri: Option[String]) {
def withMethod(method: Method): Builder[TRUE, HasUri] = copy(method = Some(method))
def withUri(uri: String): Builder[HasMethod, TRUE] = copy(uri = Some(uri))
}
implicit val init: Builder[FALSE, FALSE] = Builder[FALSE, FALSE](None, None)
//Fluent examples
val b1: Builder[TRUE, FALSE] = init.withMethod(GET)
val b2: Builder[TRUE, TRUE] = init.withMethod(GET).withUri("bar")
我希望通过允许将方法
实例转换为Builder
实例,使其更像DSL,但是,当我添加时,尝试隐式地包含init
Builder,隐式转换和类型参数的组合会混淆编译器
implicit def toMethod[HasUri](m: Method)
(implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m)
// ** ERROR **: could not find implicit value for parameter builder:
// Builder[_, HasUri]
val b3: Builder[TRUE, TRUE] = GET withUri "foo"
// However the implicit parameter is discovered fine when function is called directly
val b4: Builder[TRUE, FALSE] = toMethod(GET)
val b5: Builder[TRUE, TRUE] = toMethod(GET) withUri "foo"
除b3外,所有行都编译。显式调用toMethod
函数时,可以隐式找到生成器参数。此外,如果我删除了泛型参数(和类型安全性),代码将按预期工作
这是scala隐式转换的限制吗?还是我缺少实现这一点的正确语法
我希望隐式地发现初始生成器实例,使用户能够为自己的初始生成器提供一些生成器字段的默认值
已更新
为了保持示例的简单性,我省略了一些代码,因为我正试图修复的只是隐式转换
这里很好地概述了类型安全生成器模式:
之后,只有当Builder
具有方法和uri时,才能调用build
方法
我希望将生成器作为隐式参数发现的原因是为了在DSL中支持以下情况
url("http://api.service.org/person") apply { implicit b =>
GET assert(Ok and ValidJson)
GET / "john.doe" assert(NotFound)
POST body johnDoeData assert(Ok)
GET / "john.doe" assert(Ok and bodyIs(johnDoeData))
}
在这些情况下
url
隐式b=>
assert
方法仅在指定了uri和方法后才可用/
附加到当前uri,这仅在生成器指定了uri时可用GET url("http://api.service.org/secure/person") apply { implicit b =>
auth basic("harry", "password") assert(Ok and ValidJson)
auth basic("sally", "password") assert(PermissionDenied)
}
我觉得您的隐式解析问题并非来自Scala类型系统中的任何限制,而是取决于您在此处指定的存在类型:
implicit def toMethod[HasUri](m: Method)
(implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m)
如果我没有错的话,在这种情况下,存在主义类型被视为无。没有任何东西是每个可能的Scala类的子类,因此您的方法实际上变成:
implicit def toMethod[HasUri](m: Method)
(implicit builder: Builder[Nothing, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m)
Scala将在当前范围内查找Builder[Nothing,HasUri]的子类以提供给您的方法,并且除了Builder[Nothing,HasUri]之外,没有任何类可以匹配所需的类型,因为您的Builder类是不变的,即a
Builder[a,B]此代码现在与Scala 2.11中的代码一样工作,但是,它在Scala2.10中不起作用(我用它来编写这段原始代码)
我一直在寻找这样的原因,只能在scala lang的jira中找到这个bug
我在Scala 2.10中尝试了许多方法来解决这个问题,但都没有成功。其中包括@Edmondo1984的建议和限制HasMethod
和HasUri
参数,如下所示:
case object GET extends Method("GET")
case object POST extends Method("POST")
sealed trait TBool
trait TTrue extends TBool
trait TFalse extends TBool
case class Builder[HasMethod <: TBool, HasUri <: TBool](method: Option[Method],
uri: Option[String]) {
def withMethod(method: Method): Builder[TTrue, HasUri] = copy(method = Some(method))
def withUri(uri: String): Builder[HasMethod, TTrue] = copy(uri = Some(uri))
}
object Builder {
implicit val init: Builder[TFalse, TFalse] = Builder[TFalse, TFalse](None, None)
// Example build method
implicit class CanExecute(builder: Builder[TTrue, TTrue]) {
def execute(): String = s"Build(${builder.method} ${builder.uri}"
}
}
//Fluent examples
val b1: Builder[TTrue, TFalse] = init.withMethod(GET)
val b2: Builder[TTrue, TTrue] = init.withMethod(GET).withUri("bar")
implicit def toMethod[HasUri <: TBool](m: Method)
(implicit builder: Builder[_, HasUri]): Builder[TTrue, HasUri] = builder.withMethod(m)
// ** ERROR **: could not find implicit value for parameter builder:
// Builder[_, HasUri]
// ** BUT ** Works in Scala 2.11
val b3: Builder[TTrue, TTrue] = GET withUri "foo"
GET withUri "foo" execute ()
case对象GET扩展方法(“GET”)
案例对象POST扩展方法(“POST”)
密封式涡轮增压器
特征t真扩展t工具
特征TFalse扩展TBool
案例类生成器[HasMethod您可能想看看这一点,看看它是否揭示了隐式函数中有隐式参数的情况。我会重新考虑设计。您以一种奇怪的方式复制信息,一方面,您有一个类型构造函数参数,指示编译器在编译时是否有方法或url,同时,您还有一些只能在运行时解决的选项。问题是,您想要实现什么(应该在哪里检查类型)?如果使用类型参数,则创建包含方法和url的子类(而不是选项)。最后,您希望将一个方法提升为生成器。无需检查现有的HasUri
类型,它应始终为FALSE
?@cmbaxter,谢谢,我将尝试使用这些调试选项编译我的代码,以查看它是否有任何效果。@0\u,您是正确的,我正在使用phan复制选项的运行时状态tom类型。这是为了让编译器跟踪设置了哪些选项以及何时可以使用生成器执行请求。我不会将其建模为特征和子类,因为当您尝试跟踪更多字段和不同选项时,会出现组合爆炸(一些java fluent api可以做到这一点,而api很好地使用了它背后的代码,这很可怕)。我已更新了我的问题,以显示我正在尝试支持的DSL类型。感谢您非常详细的回答。今晚我将尝试您的建议。关于:如果解析中不涉及参数,则使用包含两个泛型参数的隐式参数有何意义?隐式参数用于DSL中的当前默认生成器。泛型正在捕获生成器的状态,build()
仅在builder[TRUE,TRUE]上可用
。toMethod
通过将方法添加到当前的默认值中来创建一个新的生成器。此默认值可能有或可能没有方法和url集,新的方法将有方法集。然后,好主意是让BuildingScope隐式地包含一个要解析的defaultBuilder
case class DefaultBuilder(m:Method) extends Builder[True,HasUri]
implicit def toMethod(m:Method) = DefaultBuilder(m)
case object GET extends Method("GET")
case object POST extends Method("POST")
sealed trait TBool
trait TTrue extends TBool
trait TFalse extends TBool
case class Builder[HasMethod <: TBool, HasUri <: TBool](method: Option[Method],
uri: Option[String]) {
def withMethod(method: Method): Builder[TTrue, HasUri] = copy(method = Some(method))
def withUri(uri: String): Builder[HasMethod, TTrue] = copy(uri = Some(uri))
}
object Builder {
implicit val init: Builder[TFalse, TFalse] = Builder[TFalse, TFalse](None, None)
// Example build method
implicit class CanExecute(builder: Builder[TTrue, TTrue]) {
def execute(): String = s"Build(${builder.method} ${builder.uri}"
}
}
//Fluent examples
val b1: Builder[TTrue, TFalse] = init.withMethod(GET)
val b2: Builder[TTrue, TTrue] = init.withMethod(GET).withUri("bar")
implicit def toMethod[HasUri <: TBool](m: Method)
(implicit builder: Builder[_, HasUri]): Builder[TTrue, HasUri] = builder.withMethod(m)
// ** ERROR **: could not find implicit value for parameter builder:
// Builder[_, HasUri]
// ** BUT ** Works in Scala 2.11
val b3: Builder[TTrue, TTrue] = GET withUri "foo"
GET withUri "foo" execute ()