提问人:MrD at KookerellaLtd 提问时间:10/6/2023 最后编辑:MrD at KookerellaLtd 更新时间:10/12/2023 访问量:95
实现“依赖注入”的技术
techniques to implement "dependency injection"
问:
我所说的“依赖注入”是指将某种指针传递给代码的 OO 概念。在 FP 中,这将使用高阶函数来完成。
在 xslt 2+ 中,似乎有这些替代方案
- 传递函数(根据函数式编程)
- 传递 psuedo 'modes',即传递一个显式构造的元素(或嵌入在某个节点结构中的值)并匹配它。
(还有其他替代方案可以使用include/import静态注入代码,其中include样式表中的模板会覆盖包含的样式表或与包含的样式表进行交互,但这更类似于“模板方法”,即静态组合)
XSLT 社区中是否有这方面的惯用/模式?
(能够以某种方式“捆绑”注入会很好,就像接口或类型类一样,这似乎可以通过将“模式”作为元素或值嵌入到另一个元素中来使用 psuedo 模式技术,或者“模式”是“捆绑”,但显然不是直接使用函数)
我没有给出明确的例子,如果你需要它们来说明我的意思,请告诉我。
实际上还有第三种选择,你返回一个 DSL,然后“解释它”,不同的上下文以不同的方式解释,这可能类似于“流水线”技术,虽然我非常喜欢它,但当你只想注入一个微小的细微差别时,它有点矫枉过正。
例子:
(都使用此 XML - 这无关紧要
<root>
<foo/>
</root>
)
#1 “高阶函数”(我认为这可能是从 Martin Honnen 之前对 HOF 的回答中窃取的)
函数“apply-function”允许“注入”一个函数,然后它执行,显然任何具有兼容签名的函数都可以被注入。这是默认的 FP 技术。
<xsl:stylesheet version="3.0"
xmlns:my="my"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Define a function that accepts a function parameter -->
<xsl:function name="my:apply-function" as="xs:integer">
<xsl:param name="input" as="xs:integer"/>
<xsl:param name="function" as="function(xs:integer) as xs:integer"/>
<xsl:sequence select="$function($input)"/>
</xsl:function>
<!-- Define a sample function to be passed as a parameter -->
<xsl:function name="my:double" as="xs:integer">
<xsl:param name="value" as="xs:integer"/>
<xsl:sequence select="$value * 2"/>
</xsl:function>
<!-- Main template -->
<xsl:template match="/">
<result>
<!-- Call my:apply-function and pass my:double as a parameter -->
<xsl:variable name="result" select="my:apply-function(5, my:double#1)"/>
<doubled-value>
<xsl:value-of select="$result"/>
</doubled-value>
</result>
</xsl:template>
</xsl:stylesheet>
#2 “伪装模式”
这样,你就可以在某个结构中显式地对你的“选择”进行编码,然后匹配它。
即“函数指针”被编码为
<Mode value='double'/>
我怀疑这种技术有更好的标签,但对我来说,模拟运行时“模式”感觉像是一种机械主义。
<xsl:stylesheet version="3.0"
xmlns:my="my"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template mode="inject" match="Mode[@value = 'double']">
<xsl:param name="value" as="xs:integer"/>
<xsl:sequence select="$value * 2"/>
</xsl:template>
<xsl:template mode="inject" match="Mode[@value = 'treble']">
<xsl:param name="value" as="xs:integer"/>
<xsl:sequence select="$value * 3"/>
</xsl:template>
<!-- Main template -->
<xsl:template match="/">
<result>
<doubled-value>
<xsl:variable name="mode">
<Mode value='double'/>
</xsl:variable>
<xsl:apply-templates mode="inject" select="$mode">
<xsl:with-param name="value" select="5"/>
</xsl:apply-templates>
</doubled-value>
</result>
</xsl:template>
</xsl:stylesheet>
#3 DSL/解释器
在这里,“callee”代码返回一些由my:makeDSL编码为xml的领域特定语言,然后由客户端特定的intepreter解释。
在这里,depedency并没有真正注入,它被外化,然后以被调用者想要的方式被嵌入,但结果是一样的。
(“nop”说明仅供说明)
<xsl:stylesheet version="3.0"
xmlns:my="my"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template mode="inject" match="Mode[@value = 'double']">
<xsl:param name="value" as="xs:integer"/>
<xsl:sequence select="$value * 2"/>
</xsl:template>
<xsl:template mode="inject" match="Mode[@value = 'treble']">
<xsl:param name="value" as="xs:integer"/>
<xsl:sequence select="$value * 3"/>
</xsl:template>
<xsl:template mode="double" match="nop"/>
<xsl:template mode="double" match="inject">
<xsl:sequence select="2 * @value"/>
</xsl:template>
<xsl:template mode="treble" match="nop"/>
<xsl:template mode="treble" match="inject">
<xsl:sequence select="3 * @value"/>
</xsl:template>
<!-- Main template -->
<xsl:template match="/">
<result>
<doubled-value>
<xsl:apply-templates mode="double" select="my:makeDSL(5)"/>
</doubled-value>
</result>
</xsl:template>
<xsl:function name="my:makeDSL">
<xsl:param name="value"/>
<nop/>
<inject value="{$value}"/>
<nop/>
</xsl:function>
</xsl:stylesheet>
答:
目前,答案似乎是否定的(没有惯用的方法),至少不是有意识的。
流水线模式我怀疑是惯用的,但在更一般的意义上,该语言鼓励一种类似于 OO 模板方法(即静态注入)的方法,其中包含/导入样式表并通过模式覆盖模板。
这并不是说它无法实现。
问题中的技术都是适用的,类似于“字典传递”的技术可以通过在 Maps 中嵌入函数来使用。
这是一个使用“monoid”的简单示例,即可以加在一起的事物。
我们有 2 个编码为 map 的 monoid“字典”,一个用于整数,一个用于字符串,我们将每个字典传递(注入)到一些代码中,这些代码“添加”一些值并返回答案。
输入 xml 是这样的
<root>
<foo value1="123" value2="456"/>
</root>
样式表将 Foo 元素第一个处理为整数,然后处理为字符串
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:kooks="kookerella.com"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
version="3.1">
<xsl:function name="kooks:stringAdd" as="xs:string">
<xsl:param name="s1" as="xs:string"/>
<xsl:param name="s2" as="xs:string"/>
<xsl:sequence select="concat($s1,$s2)"/>
</xsl:function>
<xsl:variable name="stringMonoid" as="map(*)">
<xsl:map>
<xsl:map-entry key="'zero'" select="''" as="xs:integer"/>
<xsl:map-entry key="'add'" select="kooks:stringAdd#2"/>
</xsl:map>
</xsl:variable>
<xsl:function name="kooks:integerAdd" as="xs:integer">
<xsl:param name="i1" as="xs:integer"/>
<xsl:param name="i2" as="xs:integer"/>
<xsl:sequence select="$i1 + $i2"/>
</xsl:function>
<xsl:variable name="integerMonoid" as="map(*)">
<xsl:map>
<xsl:map-entry key="'zero'" select="0" as="xs:integer"/>
<xsl:map-entry key="'add'" select="kooks:integerAdd#2"/>
</xsl:map>
</xsl:variable>
<xsl:template match="/">
<root>
<integer>
<xsl:apply-templates select="root/foo">
<xsl:with-param name="monoid" select="$integerMonoid"/>
</xsl:apply-templates>
</integer>
<string>
<xsl:apply-templates select="root/foo">
<xsl:with-param name="monoid" select="$stringMonoid"/>
</xsl:apply-templates>
</string>
</root>
</xsl:template>
<xsl:template match="foo">
<xsl:param name="monoid"/>
<result
value1="{@value1}"
value2="{@value2}"
zero="{map:get($monoid,'zero')}"
sum="{map:get($monoid,'add')(@value1,@value2)}"/>
</xsl:template>
</xsl:stylesheet>
这导致了这个
<root>
<integer>
<result value1="123" value2="456" zero="0" sum="579"/>
</integer>
<string>
<result value1="123" value2="456" zero="" sum="123456"/>
</string>
</root>
当然,这只是将函数作为参数传递的变体,即高阶函数,但是将函数嵌入映射中的能力使得将它们打包成“类型类”之类的东西成为可能。
(注意 OOers,这些伪装类型类不是对象,如果你把它们看作是对象,那么它们就是没有状态的对象,它们的目的只是打包函数,而不是封装状态)
评论
<xsl:param name="my-func" as="function(item(), node()?) as node()*"/>