Implicit conversion not working with type builder pattern

I use the Scala type constructor template safe for a simple rest request. This works great as a seamless 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") 

I would like to make it more DSL-like by allowing the instance of Method converted to an instance of Builder . However, when I add an attempt to implicitly include the constructor init combination of implicit conversion parameters and the type confuse the compiler.

 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" 

All lines are compiled except b3. When the toMethod function toMethod called explicitly, the builder parameter can be found implicitly. Also, if I remove the general arguments (and the security type), the code works as expected.

Is this a limitation in Scala implicit conversions? Or am I missing the correct syntax to achieve this?

I want to open an instance of the initial instance implicitly, to allow users to provide their own initial builder with default values ​​for some fields of the builder.

Update

I left a piece of code to simplify the example, since this is just an implicit conversion that I'm trying to fix.

A line type type template is very well described here: http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html

Then you can call the build method only after the Builder has a method and uri.

The reason I want to open the builder as an implicit parameter is to support the following case in 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)) } 

In these cases

  • A new constructor is created with the specified uri using url
  • Then it is reused on the close side as implicit b =>
  • The assert method is available only because the uri and method are specified
  • / joins the current uri, this is only available because the constructor has uri specified.

Another example where the method and uri are indicated

 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) } 
+4
source share
2 answers

This code now works as-is in Scala 2.11, however it does not work in Scala 2.10 (which I used to write this source code).

I was looking for a reason why this could be so, and you can only see this error in scala -lang jira.

https://issues.scala-lang.org/browse/SI-3346

I tried several methods to solve this problem in Scala 2.10, but could not. They included the @ Edmondo1984 clause and limited the parameters to HasMethod and HasUri , as shown below:

 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 () 
+1
source

I have a feeling that your problem with implicit resolution does not come from any restrictions in the Scala type system, but it depends on the type of existence that you specify here:

 implicit def toMethod[HasUri](m: Method) (implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m) 

If I am not mistaken, in this case the existential type is considered as Nothing. Nothing is a subclass of all possible Scala classes, so your method actually becomes:

 implicit def toMethod[HasUri](m: Method) (implicit builder: Builder[Nothing, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m) 

Scala will then look into the current search area to find the subclass Builder [Nothing, HasUri] to provide your method, and there is no class that can match the type you want, except Builder [Nothing, HasUri], because your builder class is invariant, i.e. . a Builder[A,B]<:<Builder[C,D] iff A=:=C & B=:=D

You have two options:

  • Add parameter to signature, toMethod [HasUri] becomes toMethod [A, HasUri]
  • Exploit Scala proper implementation of type variances

Since you want your Builder [A, HasUri] to be a subclass of Builder [Nothing, HasUri] and

 Nothing <:< A for any A 

you want to ensure that this Builder[A,HasUri] <:< Builder[B,HasUri] iff B<:<A ie Builder is invariant in its first type parameter. You apply controvariance by putting a-simbol before the type:

Builder[-HasMethod, HasUri] is invariant in HasMethod and invariant in HasUri


Conclusion

Type systems are powerful, but it is not necessary to use complex templates even for simple tasks:

  • HasUri is not inferred from m, since it is a type parameter from the toMethod method
  • HasMethod is not displayed because you delete it with _

What is the point of having an implicit parameter with two generics arguments if the arguments are not involved in your resolution? I would just write:

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

When you are finished with situations like someone has already said, this is because your design does not fit this problem. Can you explain why the builder should be hidden in the method?

 implicit def toMethod(m:Method) = DefaultBuilder(m) 
+1
source

Source: https://habr.com/ru/post/1480382/


All Articles