gpt4 book ai didi

scala - 无形案例研究

转载 作者:行者123 更新时间:2023-12-02 03:31:40 25 4
gpt4 key购买 nike

在我的应用程序中,我有一堆能够呈现 Html 的组件:

class StandardComponent {
def render: Html
}

它们在运行时由 ComponentBuilderComponentDefinition 对象实例化,它提供对运行时数据的访问:

class ComponentBuilder {
def makeComponent(componentDef: ComponentDefinition): StandardComponent
}

然后有几个助手可以促进组件内子组件的渲染:

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]

问题是,一个组件通常需要使用多个这样的 from* 调用。虽然它们被设计成可嵌套的,但它可能会变得有点乱:

implicit val componentBuilder: ComponentBuilder = ???

val subComponent: ComponentDefinition = ???
val subComponents: Seq[ComponentDefinition] = ???
val subComponentOpt: Option[ComponentDefinition] = ???

fromComponent(subComponent) { html =>
fromComoponents(subComponents) { htmls =>
fromOptionalComponent(subComponentOpt) { optHtml =>
???
}
}
}

我想做的大致是这样的:

withSubComponents(
subComponent, subComponents, subComponentOpt
) { case (html, htmls, optHtml) => /* as Html, Seq[Html], and Option[Html] */
???
}

因此,我想让 withSubComponents 在其参数中可变,并且我想让它在第二个参数列表中采用的闭包具有一个参数列表,该参数列表取决于 arity 中的第一个参数列表和类型。理想情况下,它还采用隐式 ComponentBuilder,就像各个助手所做的那样。这是理想的语法,但我愿意接受其他选择。我可以提供一些我目前所拥有的例子,但到目前为止我所拥有的只是想法。感觉我需要制作 CoProduct 的 HList,然后我需要将两个参数联系在一起的方法。

最佳答案

改进 DSL 的第一步可以是将方法移动到像这样的隐式转换:

implicit class SubComponentEnhancements[T](subComponent: T)(
implicit cb: ComponentBuilder[T]) {

def fromComponent(f: cb.HtmlType => Future[Html]): Future[Html] = ???
}

请注意,我声明了 fromComponent 对每个定义了 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))
}
}

请注意,每个组件构建器都指定了一个与 ComponentBuilder 的类型同步的 HtmlType。容器类型的构建器只需为它们的内容请求一个组件构建器。这使我们无需过多的额外努力即可嵌套不同的组合。我们可以进一步推广这个概念,但现在这没问题。

至于单一组件构建器,您可以定义得更通用,允许您拥有不同类型的组件定义。可以使用位于多个不同位置的 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 ???

这对我来说看起来很神奇,不幸的是 OptionSeq 本身有一个 flatMap 函数,所以我们需要隐藏它们。下面的代码看起来很干净,让我们有机会隐藏 flatMapmap 方法。

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))
}

不同的 build 者也需要改变

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 的方式,但它至少为您提供了一些工具来创建一个非常酷的 DSL。

关于scala - 无形案例研究,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26318713/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com