fintrospect
fintrospect copied to clipboard
Auto should not use implicit conversion functions
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).
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?
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 map
ing 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)
}