Monday, 15 September 2014

scala - Shapeless case study -



scala - Shapeless case study -

in application, have bunch of components capable of rendering html:

class standardcomponent { def render: html }

they instantiated @ run-time componentdefinition objects componentbuilder, provides access run-time data:

class componentbuilder { def makecomponent(componentdef: componentdefinition): standardcomponent }

then there several helpers facilitate rendering of sub-components within components:

def fromcomponent(componentdef: componentdefinition)(htmlfn: html => future[html])(implicit componentbuilder: componentbuilder): future[html] def fromcomponents(componentdefs: seq[componentdefinition])(htmlfn: seq[html] => future[html])(implicit componentbuilder: componentbuilder): future[html] def fromoptionalcomponent(componentdefopt: option[componentdefinition])(htmlfn: option[html] => future[html])(implicit componentbuilder: componentbuilder): future[html] def fromcomponentmap[k](componentdefmap: map[k, componentdefinition])(htmlfn: map[k, html] => future[html])(implicit componentbuilder: componentbuilder): future[html]

the problem oftentimes, component needs utilize several of these from* calls. although designed nestable, can turn bit of mess:

implicit val componentbuilder: componentbuilder = ??? val subcomponent: componentdefinition = ??? val subcomponents: seq[componentdefinition] = ??? val subcomponentopt: option[componentdefinition] = ??? fromcomponent(subcomponent) { html => fromcomoponents(subcomponents) { htmls => fromoptionalcomponent(subcomponentopt) { opthtml => ??? } } }

what i'd able like:

withsubcomponents( subcomponent, subcomponents, subcomponentopt ) { case (html, htmls, opthtml) => /* html, seq[html], , option[html] */ ??? }

so, want create withsubcomponents variadic in arguments, , want create closure takes in sec argument list have argument list depends on first argument list in arity , type. ideally, takes implicit componentbuilder, individual helpers do. that's ideal syntax, i'm open alternatives. provide examples of have far, have ideas far. feels need create hlist of coproduct, , need way tie 2 arguments together.

the first step in improving dsl can move methods implicit conversion this:

implicit class subcomponentenhancements[t](subcomponent: t)( implicit cb: componentbuilder[t]) { def fromcomponent(f: cb.htmltype => future[html]): future[html] = ??? }

note declared fromcomponent valid each type t has componentbuilder defined. can see imagined componentbuilder have htmltype. in illustration seq[html], option[html], etc. componentbuilder looks this:

trait componentbuilder[t] { type htmltype def render(componentdef: t): htmltype }

i imagined componentbuilder able render component type of html. let's declare component builders able phone call fromcomponent method on different types.

object componentbuilder { implicit def single = new componentbuilder[componentdefinition] { type htmltype = html def render(componentdef: componentdefinition) = { // create standard component component definition val standardcomponent = new standardcomponent standardcomponent.render } } implicit def seq[t]( implicit cb: componentbuilder[t]) = new componentbuilder[seq[t]] { type htmltype = seq[cb.htmltype] def render(componentdef: seq[t]) = componentdef.map(c => cb.render(c)) } implicit def option[t]( implicit cb: componentbuilder[t]) = new componentbuilder[option[t]] { type htmltype = option[cb.htmltype] def render(componentdef: option[t]) = componentdef.map(c => cb.render(c)) } }

notice each of component builders specifies htmltype in sync type of componentbuilder. builders container types request component builder contents. allows nest different combinations without much effort. generalize concept further, fine.

as single component builder, define more generically, allowing have different types of component definitions. converting them standard component done using converter located in serveral different places (companion object of x, companion object of converter or separate object users need import manually).

trait converter[x] { def convert(c:x):standardcomponent } object componentdefinition { implicit val defaultconverter = new converter[componentdefinition] { def convert(c: componentdefinition):standardcomponent = ??? } } implicit def single[x](implicit converter: converter[x]) = new componentbuilder[x] { type htmltype = html def render(componentdef: x) = converter.convert(componentdef).render }

anyway, code looks following:

subcomponent fromcomponent { html => subcomponents fromcomponent { htmls => subcomponentopt fromcomponent { opthtml => ??? } } }

this looks familiar pattern, let's rename methods:

subcomponent flatmap { html => subcomponents flatmap { htmls => subcomponentopt map { opthtml => ??? } } }

note in wishful thinking space, above code not compile. if had way of making compile write following:

for { html <- subcomponent htmls <- subcomponents opthtml <- subcomponentopt } yield ???

that looks pretty amazing me, unfortunately option , seq have flatmap function themselves, need hide those. next code looks clean , gives oportunity hide flatmap , map methods.

trait wrapper[+a] { def map[b](f:a => b):wrapper[b] def flatmap[b](f:a => wrapper[b]):wrapper[b] } implicit class htmlenhancement[t](subcomponent:t) { def html:wrapper[t] = ??? } { html <- subcomponent.html htmls <- subcomponents.html opthtml <- subcomponentopt.html } yield ???

as can see still in wishful thinking space, let's see if can fill in blanks.

case class wrapper[+a](value: a) { def map[b](f: => b) = wrapper(f(value)) def flatmap[b](f: => wrapper[b]) = f(value) } implicit class htmlenhancement[t](subcomponent: t)( implicit val cb: componentbuilder[t]) { def html: wrapper[cb.htmltype] = wrapper(cb.render(subcomponent)) }

the implementation not complicated because can utilize tools created earlier. note during wishful thinking returned wrapper[t] while needed html, using htmltype component builder.

to improve type inference, alter componentbuilder slightly. alter htmltype type fellow member type parameter.

trait componentbuilder[t, r] { def render(componentdef: t): r } implicit class htmlenhancement[t, r](subcomponent: t)( implicit val cb: componentbuilder[t, r]) { def html:wrapper[r] = wrapper(cb.render(subcomponent)) }

the different builders need alter well

object componentbuilder { implicit def single[x](implicit converter: converter[x]) = new componentbuilder[x, html] { def render(componentdef: x) = converter.convert(componentdef).render } implicit def seq[t, r]( implicit cb: componentbuilder[t, r]) = new componentbuilder[seq[t], seq[r]] { def render(componentdef: seq[t]) = componentdef.map(c => cb.render(c)) } implicit def option[t, r]( implicit cb: componentbuilder[t, r]) = new componentbuilder[option[t], option[r]] { def render(componentdef: option[t]) = componentdef.map(c => cb.render(c)) } }

the end result looks this:

val wrappedhtml = { html <- subcomponent.html htmls <- subcomponents.html opthtml <- subcomponentopt.html } yield { // interesting stuff html htmls ++ opthtml.toseq :+ html } // type of `result` `seq[html]` val result = wrappedhtml.value // or val wrapper(result) = wrappedhtml

as might have noticed, skipped future, can add together please.

i not sure if how envisioned dsl, @ to the lowest degree gives tools create cool one.

scala shapeless

No comments:

Post a Comment