如何在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>应该得到什么textCharacterFontRegionTransformer<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对于所有abc ),上面的实现似乎满足这个需求。 但是,严格地说,它也应该有一个中性的元素,例如,对于任何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) } } }