在提高DSL的第一步可以是方法移動到這樣的隱式轉換:我聲明fromComponent
implicit class SubComponentEnhancements[T](subComponent: T)(
implicit cb: ComponentBuilder[T]) {
def fromComponent(f: cb.HtmlType => Future[Html]): Future[Html] = ???
}
注是有效的用於具有定義的ComponentBuilder
每種類型T
。正如你所看到的,我也想象ComponentBuilder
有一個HtmlType
。在您的例子將是Seq[Html]
,Option[Html]
等ComponentBuilder
現在看起來是這樣的:
trait ComponentBuilder[T] {
type HtmlType
def render(componentDef: T): HtmlType
}
我又想到了ComponentBuilder
能夠爲組件呈現爲某種類型的Html
。讓我們聲明一些組件構建器爲了能夠調用不同類型的fromComponent
方法。
object ComponentBuilder {
implicit def single =
new ComponentBuilder[ComponentDefinition] {
type HtmlType = Html
def render(componentDef: ComponentDefinition) = {
// Create standard component from a 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))
}
}
注意,每個組件助洗劑的指定了一個HtmlType
即在同步與ComponentBuilder
的類型。容器類型的構建器只需要爲其內容請求組件構建器。這使我們能夠嵌套不同的組合,而不需要額外的努力。我們可以進一步概括這個概念,但現在這沒問題。
至於single
組件構建器,您可以更一般地定義,允許您擁有不同類型的組件定義。將它們轉換爲標準組件可以使用Converter
,它可以位於多個不同的地方(伴侶對象X
,伴侶對象Converter
或用戶需要手動導入的單獨對象)。
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
}
不管怎麼說,現在該代碼如下所示:
subComponent fromComponent { html =>
subComponents fromComponent { htmls =>
subComponentOpt fromComponent { optHtml =>
???
}
}
}
這看起來像一個熟悉的模式,讓我們重命名方法:
subComponent flatMap { html =>
subComponents flatMap { htmls =>
subComponentOpt map { optHtml =>
???
}
}
}
請注意,我們是在一廂情願思維空間,上面的代碼不會編譯。如果我們做的一些方式,但是編譯我們可以寫一些像下面這樣:
for {
html <- subComponent
htmls <- subComponents
optHtml <- subComponentOpt
} yield ???
這看起來非常令人驚異的是我,可惜Option
和Seq
有flatMap
功能本身,所以我們需要隱藏的。以下代碼看起來很乾淨,給我們提供了隱藏flatMap
和map
方法的機會。
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] = ???
}
for {
html <- subComponent.html
htmls <- subComponents.html
optHtml <- subComponentOpt.html
} yield ???
正如你所看到的,我們仍然處於一廂情願的空間,讓我們看看我們是否可以填補空白。
case class Wrapper[+A](value: A) {
def map[B](f: A => B) = Wrapper(f(value))
def flatMap[B](f: A => Wrapper[B]) = f(value)
}
implicit class HtmlEnhancement[T](subComponent: T)(
implicit val cb: ComponentBuilder[T]) {
def html: Wrapper[cb.HtmlType] = Wrapper(cb.render(subComponent))
}
實現並不複雜,因爲我們可以使用我們之前創建的工具。請注意,在一廂情願的想法中,我返回了一個Wrapper[T]
,而我們實際上需要html,所以我現在使用組件構建器中的HtmlType
。
爲了改進類型推斷,我們將稍微更改ComponentBuilder
。我們將將HtmlType
類型的成員更改爲類型參數。
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))
}
不同的建設者需要的改變以及
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))
}
}
最終的結果現在看起來是這樣的:
val wrappedHtml =
for {
html <- subComponent.html
htmls <- subComponents.html
optHtml <- subComponentOpt.html
} yield {
// Do some interesting stuff with the html
htmls ++ optHtml.toSeq :+ html
}
// type of `result` is `Seq[Html]`
val result = wrappedHtml.value
// or
val Wrapper(result) = wrappedHtml
正如你可能已經注意到,我跳過了Future
,你可以隨你自己補充一點。
我不確定您是如何設想您的DSL的,但它至少會爲您提供一些工具來創建非常酷的工具。
哇,我不確定這與我們實際工作的方式是一致的(我在我的問題中大大簡化以試圖強調問題的本質),但這是驚人的,我相信甚至如果我們不能使用整個解決方案,我們可能會使用件。特別是,我同意利用'for ... yield'語法會有所幫助。我希望我可以給你更多的讚揚這樣一個深思熟慮的職位。 – acjay 2014-10-12 23:47:25
那麼,'for ... yield'語法(在我看來)最適合於進程(或步驟)。在你的情況下,它似乎更多的是結構。我還沒有找到理想的Scala結構解決方案。 – EECOLOR 2014-10-13 00:45:01