我正在使用Scala类型安全构建器模式来执行简单的休息请求。这很好用,作为一个流利的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](
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")
我想通过允许将Method
实例转换为Builder
实例来使其更像DSL,但是当我添加try时隐式包含init
构建器的组合隐式转换和类型参数混淆了编译器。
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隐式转换的限制吗?或者我错过了正确的语法来实现这个目标?
我想隐式发现初始构建器实例,以使用户能够为某些构建器字段的默认值提供自己的初始构建器。
更新
我已经离开了一些代码以保持示例简单,因为它只是我想要修复的隐式转换。
这里概述了类型安全的构建器模式:http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html
之后,只有build
有方法和uri后才能调用Builder
方法。
我想将构建器作为隐式参数发现的原因是在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
implicit b =>
assert
方法仅可用,因为已指定了uri和方法/
附加到当前的uri,这只是因为构建器指定了uri而可用。指定method和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)
}
答案 0 :(得分:1)
我觉得您的隐式解决问题不是来自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,B]<:<Builder[C,D]
iff A=:=C & B=:=D
因此,您有两种选择:
因为你想强制你的Builder [A,HasUri]是Builder的子类[Nothing,HasUri]和
Nothing <:< A for any A
你想强制执行Builder[A,HasUri] <:< Builder[B,HasUri]
iff B<:<A
,即Builder在其第一个类型参数中是有争议的。你可以通过在类型前面加一个 - simbol来强制实施控制:
Builder[-HasMethod, HasUri]
在HasMethod中是有争议的,在HasUri中是不变的
<强>结论强>
类型系统功能强大,但即使是简单的任务,也不一定要使用复杂的模式:
如果参数不包含在您的分辨率中,有两个泛型参数的隐式参数有什么意义?我只想写:
case class DefaultBuilder(m:Method) extends Builder[True,HasUri]
当你最终遇到这种情况时,正如某人已经说过的那样,这是因为你的设计错误。你能解释为什么构建器必须隐含在toMethod中吗?
implicit def toMethod(m:Method) = DefaultBuilder(m)
答案 1 :(得分:1)
此代码现在在Scala 2.11中按原样运行,但它在Scala 2.10(我用来编写此原始代码)时不起作用。
我一直在寻找可能出现这种情况的原因,只能在scala-lang的jira中看到这个错误。
https://issues.scala-lang.org/browse/SI-3346
我在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 ()