【发布时间】:2013-05-12 21:47:49
【问题描述】:
我正在使用 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,但是当我添加尝试隐式包含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 函数时,可以隐式找到 builder 参数。此外,如果我删除通用参数(和类型安全),代码将按预期工作。
这是 scala 隐式转换的限制吗?还是我错过了实现此目的的正确语法?
我想隐式发现初始构建器实例,以使用户能够为他们自己的初始构建器提供一些构建器字段的默认值。
更新
为了使示例简单,我省略了一些代码,因为它只是我要修复的隐式转换。
类型安全的构建器模式在这里得到了很好的概述:http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html
之后,您只能在 Builder 具有方法和 uri 时调用 build 方法。
我想发现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指定的 uri 创建了一个新构建器 - 然后在闭包中重复使用
implicit b => -
assert方法只有在指定了 uri 和方法后才可用 -
/附加到当前 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)
}
【问题讨论】:
-
你可能想看看这个,看看它是否能说明你的隐式函数有一个隐式参数。 stackoverflow.com/questions/5080406/…
-
我会重新考虑设计。您正在以一种奇怪的方式复制信息 - 一方面您有一个类型构造函数参数指示构建器在编译时是否有方法或 url,同时您有只能在运行时解决的选项。问题是,你想达到什么目的(应该在哪里检查类型)?如果您使用类型参数,请创建带有方法和 url(而不是选项)的子类。最后,您想将方法提升为构建器。无需检查现有的
HasUri类型,它应该始终是FALSE? -
@cmbaxter,谢谢,我将尝试使用这些调试选项编译我的代码,看看它是否有任何启发。
-
@0__,您说得对,我正在使用幻像类型复制选项的运行时状态。这是为了让编译器跟踪设置了哪些选项以及何时可以使用构建器执行请求。我没有将其建模为特征和子类,因为当您尝试跟踪更多字段和不同选项时会发生组合爆炸(一些 java fluent api 会这样做,而 api 很好地使用它背后的代码是可怕的)。我已经更新了我的问题,以显示我正在尝试支持的 DSL。
标签: scala dsl type-inference implicit-conversion