scala – 不使用类型安全构建器模式的隐式转换

我正在使用
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函数时,可以隐式找到builder参数.此外,如果我删除通用参数(和类型安全性),代码按预期工作.

这是scala隐式转换的限制吗?或者我错过了正确的语法来实现这一目标?

我想隐式发现初始构建器实例,以使用户能够为某些构建器字段的默认值提供自己的初始构建器.

更新

我已经留下了一些代码来保持示例简单,因为它只是我试图修复的隐式转换.

这里列出了类型安全的构建器模式:http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html

之后,只有在Builder有方法和uri后才能调用构建方法.

我想将构建器作为隐式参数发现的原因是在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创建新的构建器
>然后在闭包侧重复使用,因为隐式b =>
>断言方法仅可用,因为已指定了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)
}

最佳答案 我觉得你的隐式解决问题不是来自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]之外没有可以匹配所需类型的类,因为您的构建器类是不变的,即构建者[A,B]<:< Builder [C,D] iff A =:= C& B =:= d 因此,您有两种选择:
>在签名中添加一个参数,toMethod [HasUri]变为toMethod [A,HasUri]
>利用Scala正确实现类型差异

既然你想强制你的Builder [A,HasUri]是Builder [Nothing,HasUri]的子类,

Nothing <:< A for any A

你想强制执行Builder [A,HasUri]<:< Builder [B,HasUri] iff B<:< A,即Builder在其第一个类型参数中是conovariant.你通过在类型前放一个 – simbol来强制执行控制: Builder [-HasMethod,HasUri]在HasMethod中是conovariant,在HasUri中是不变的 结论 类型系统功能强大,但即使是简单的任务,也不一定要使用复杂的模式:
> HasUri不是从m推断的,因为它是方法toMethod的类型参数
>不推断HasMethod,因为您使用_擦除它

如果参数不涉及您的分辨率,那么使用两个泛型参数的隐式参数有什么意义?我只想写:

case class DefaultBuilder(m:Method) extends Builder[True,HasUri]

当你最终遇到这种情况时,正如某人已经说过的那样,这是因为你的设计错误.你能解释为什么构建器必须隐含在toMethod中吗?

implicit def toMethod(m:Method) = DefaultBuilder(m)
点赞