如何在Kotlin中构建兼容的参数/结果类型的函数?
我有一个这样的interface
:
interface FontRegionTransformer<R> { fun transform(region: R, textCharacter: TextCharacter): R }
我不是类别理论方面的专家,但是正如我以前学过的,这个结构是一个monoid (是吗?) ,我可以将任何数量的带R
和返回R
的函数结合起来。
这就是我现在所拥有的:
var image = source.getSubimage(meta.x * width, meta.y * height, width, height) regionTransformers.forEach { image = it.transform(image, textCharacter) }
这工作,但我有一个问题:如何将FontRegionTransformer
List
组合到单个函数? 我可以做到这一点,而不添加一个compose
功能到我的界面? 我尝试reduce
但没有点击。
澄清:我想实现的是将存储在regionTransformers
的函数regionTransformers
成一个函数,而不是循环:
var image = source.getSubimage(meta.x * width, meta.y * height, width, height) regionTransformers.forEach { image = it.transform(image, textCharacter) }
我想要这样的东西:
var image = source.getSubimage(meta.x * width, meta.y * height, width, height) return combinedTransformers.invoke(image)
对于组合定义,不太清楚,当调用组合变换器时,第二个FontRegionTransformer<R>
应该得到什么textCharacter
的FontRegionTransformer<R>
。 在这里,我假设它是传递给调用的相同的textCharacter
,并自然传递给第一个变换器。
您可以将自定义组合操作实现为FontRegionTransformer<R>
的扩展:
fun <R> FontRegionTransformer<R>.compose(other: FontRegionTransformer<R>) = object : FontRegionTransformer<R> { override fun transform(region: R, textCharacter: TextCharacter): R { val firstResult = this@compose.transform(region, textCharacter) return other.transform(firstResult, textCharacter) } }
您可以添加中infix
修饰符来compose
使用中缀表示a compose b
,或者使其重载operator +
或*
,如果您想将其称为a * b
。 或者使用非扩展顶级函数来compose(a, b)
调用。
然后你可以FontRegionTransformer
两个FontRegionTransformer
s:
val composed = first.compose(second)
要将变压器列表组合成一个,请使用reduce
:
val transformers: List<FontRegionTransformer<SomeType>> = TODO() val composition = transformers.reduce { a, b -> a.compose(b) }
对于FontRegionTransformer<R>
是monoid,组合操作应该是关联的( a ∘ (b ∘ c)
应该等于(a ∘ b) ∘ c
all (a ∘ b) ∘ c
对于所有a
, b
和c
),上面的实现似乎满足这个需求。 但是,严格地说,它也应该有一个中性的元素,例如,对于任何a
a ∘ n = n ∘ a = a
都是a
a ∘ n = n ∘ a = a
a
。 这两个要求不能用Kotlin型系统表示,而应该是合同的一部分。
一个单一语句的解决方案是内联compose
reduce
调用:
val composition = transformers.reduce { a, b -> object : FontRegionTransformer<SomeType> { override fun transform(region: SomeType, textCharacter: TextCharacter) = a.transform(region, textCharacter).let { b.transform(it, textCharacter) } } }