scala - Implicit conversion not working with type-safe builder pattern -
i using scala type-safe builder pattern simple rest request. works great fluent api.
sealed abstract class method(name: string) case object 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 make more dsl-like allowing method
instance converted builder
instance, when add try implicitly include init
builder combination of implicit conversion , type parameters confuse compiler.
implicit def tomethod[hasuri](m: method) (implicit builder: builder[_, hasuri]): builder[true, hasuri] = builder.withmethod(m) // ** error **: not find implicit value parameter builder: // builder[_, hasuri] val b3: builder[true, true] = withuri "foo" // implicit parameter discovered fine when function called directly val b4: builder[true, false] = tomethod(get) val b5: builder[true, true] = tomethod(get) withuri "foo"
all lines compile except b3. when tomethod
function called explicitly builder parameter can found implicitly. if remove generic arguments (and type-safety) code works expected.
is limitation in scala's implicit conversions? or missing correct syntax achieve this?
i want discover initial builder instance implicitly enable users provide own initial builder default values of builder's fields.
updated
i have left of code out keep example simple, since implicit conversion trying fix.
the type-safe builder pattern outlined here: http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html
afterwards can call build
method once builder
has method , uri.
the reason want discover builder implicit parameter support following case in dsl.
url("http://api.service.org/person") apply { implicit b => assert(ok , validjson) / "john.doe" assert(notfound) post body johndoedata assert(ok) / "john.doe" assert(ok , bodyis(johndoedata)) }
in these cases
- a new builder created specified uri
url
- this reused in side closure
implicit b =>
- the
assert
method available because uri , method have been specified - the
/
appends current uri, available because builder has uri specified.
another example method , uri specified
get url("http://api.service.org/secure/person") apply { implicit b => auth basic("harry", "password") assert(ok , validjson) auth basic("sally", "password") assert(permissiondenied) }
i have feeling implicit resolution problem not come limitation in scala type system, depends on existential type specify here:
implicit def tomethod[hasuri](m: method) (implicit builder: builder[_, hasuri]): builder[true, hasuri] = builder.withmethod(m)
if not wrong, existential type treated nothing in case. nothing subclass of every possible scala class, method becomes in fact:
implicit def tomethod[hasuri](m: method) (implicit builder: builder[nothing, hasuri]): builder[true, hasuri] = builder.withmethod(m)
scala in current scope find subclass of builder[nothing,hasuri] provide method , there no class can match required type except builder[nothing,hasuri], because builder class invariant, i.e. builder[a,b]<:<builder[c,d]
iff a=:=c & b=:=d
you have therefore 2 options:
- add parameter signature, tomethod[hasuri] becomes tomethod[a,hasuri]
- exploit scala correct implementation of type variances
since want enforce builder[a,hasuri] subclass of builder[nothing,hasuri] and
nothing <:<
you want enforce builder[a,hasuri] <:< builder[b,hasuri]
iff b<:<a
i.e. builder controvariant in first type parameter. enforce controvariance putting - simbol in front of type:
builder[-hasmethod, hasuri]
controvariant in hasmethod , invariant in hasuri
conclusion
type systems powerful, it's not mandatory use complex patterns simple tasks:
- hasuri not inferred m, type parameter method tomethod
- hasmethod not inferred, because erase using _
what point of having implicit parameter 2 generics arguments if arguments not involved in resolution ? write:
case class defaultbuilder(m:method) extends builder[true,hasuri]
when end these kind of situations, has said, it's because design wrong problem. can explain why builder has implicit in tomethod?
implicit def tomethod(m:method) = defaultbuilder(m)
Comments
Post a Comment