fintrospect icon indicating copy to clipboard operation
fintrospect copied to clipboard

Auto should not use implicit conversion functions

Open ncreep opened this issue 7 years ago • 2 comments

Hi,

Currently io.fintrospect.formats.Auto uses implicit functions in various methods. E.g.:

def Out[OUT](svc: Service[Request, OUT], successStatus: Status = Status.Ok)
            (implicit transform: OUT => R): Service[Request, Response]

This can be problematic, for example in cases where one is trying to serialize raw JSON (e.g., Play's JsValue). Since in this case OUT =:= R , and Fintrospect's built-in implicit conversion competes with the standard library's <:< (which extends Function1).

The workaround for this particular problem is to pass the implicit argument explicitly. But a more general solution would be to introduce a dedicated type for the conversions that take place in Auto. This will avoid polluting the implicit scope with a common type and thus won't compete with the standard library (or anything else for that matter).

ncreep avatar Jun 04 '17 11:06 ncreep

Thanks for the suggestion. Have you got a quick Gist to demonstrate the problem in action, or even better something to show the kind of solution that you were thinking of?

daviddenton avatar Jun 04 '17 20:06 daviddenton

I can try both...

I'll use Play for the example, but it's problematic for all other formats as well. Given this code:

import io.fintrospect.formats.Play.Auto._

val js: JsValue = ???
Out {
  Service.mk { _: Request =>
    Future(js)
  }
}

You get this compilation error:

Error:(99, 9) ambiguous implicit values:
 both method $conforms in object Predef of type [A]=> <:<[A,A]
 and method tToJsValue in object Auto of type [T](implicit db: play.api.libs.json.Writes[T])T => play.api.libs.json.JsValue
 match expected type play.api.libs.json.JsValue => play.api.libs.json.JsValue
Error occurred in an application involving default arguments.
    Out {

My proposed solution would be to introduce a new function-like type:

trait ToResponse[-A, +B] {
  def apply(a: A): B
}

object ToResponse {
  def apply[A, B](f: A => B): ToResponse[A, B] = new ToResponse {
    def apply(a: A): B = f(a)
  } 
}

(you might consider extending Function1 which will enable maping ToResponse over things, but that adds some implicit-scope pollution)

Now in the Auto type you rewrite the methods to take implicit ToResponse instances, e.g.:

def Out[OUT](svc: Service[Request, OUT], successStatus: Status = Status.Ok)
            (implicit transform: ToResponse[OUT, R]): Service[Request, Response]

And the relevant implicit functions should be wrapped accordingly, for example in io.fintrospect.formats.Play:

implicit def tToJsValue[T](implicit db: Writes[T]): ToResponse[T, JsValue] = 
  ToResponse { (t: T) => 
    JsonFormat.encode[T](t)
  }

ncreep avatar Jun 05 '17 12:06 ncreep