Как создавать функции с совместимыми параметрами / результатами в Котлине?

У меня есть interface который выглядит так:

 interface FontRegionTransformer<R> { fun transform(region: R, textCharacter: TextCharacter): R } 

Я не эксперт в теории категорий, но, как я узнал ранее, эта структура является моноидом (это?), И я могу комбинировать любое количество функций, которые принимают R и возвращают R вместе.

Это то, что я имею прямо сейчас:

 var image = source.getSubimage(meta.x * width, meta.y * height, width, height) regionTransformers.forEach { image = it.transform(image, textCharacter) } 

Это работает, но у меня возникает вопрос: как объединить List FontRegionTransformer s с одной функцией? Могу ли я это сделать без добавления функции compose в мой интерфейс? Я попробовал его с reduce но не нажал.

Уточнение: я хотел бы добиться объединения функций, хранящихся в 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) 

Для определения композиции не совсем понятно, когда вызывается textCharacter трансформатор, какой должен получить FontRegionTransformer<R> должен получить второй 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 чтобы использовать нотацию infix a compose b или сделать ее перегрузкой оператора + или * , если вы хотите называть его как 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> был FontRegionTransformer<R> , операция композиции должна быть ассоциативной ( a ∘ (b ∘ c) должна быть эквивалентна (a ∘ b) ∘ c для всех a , b и c ), и приведенная выше реализация, похоже, удовлетворяет этому требование. Но, строго говоря, он должен также иметь нейтральный элемент, такой, что a ∘ n = n ∘ a = a для любого a . Эти два требования не могут быть выражены в терминах системы типа Котлин и должны быть частью контракта.


Решение с одним заявлением состоит в том, чтобы вложить вклад в вызов 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) } } }