Как вызвать функции расширения из общего кода - PullRequest
0 голосов
/ 07 мая 2018

С учетом следующих классов и функций расширения

class Foo 
class Bar

fun Foo.run() = "Foo.run"
fun Bar.run() = "Bar.run"

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar())
    val y = x.map({a -> a.run()}) //compiler error
    println("Hello, world!")
}

можно ли назвать run общим способом?

Каким-то образом это попытка эмулировать protocol s и extension s Swift при расширении класса через интерфейс, либо невозможно или нежелательно .

Ответы [ 3 ]

0 голосов
/ 07 мая 2018

Вам просто нужен общий тип, и вместо этого расширьте его. Простой маркер интерфейс поможет здесь:

interface Common
class Foo: Common
class Bar: Common

fun Common.run() = "run"

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar())
    val y = x.map {a -> a.run() }
}

Или с sealed классами (как вы предложили в ОП):

sealed class Common {
    class Foo: Common()
    class Bar: Common()
}

fun <T: Common> T.run() = "run"

fun main(args: Array<String>) {
    val x = listOf(Common.Foo(), Common.Bar())
    val y = x.map {a -> a.run() }
}
0 голосов
/ 07 мая 2018

Ваш список имеет наименьший общий тип для Foo и Bar - Any, вы должны проверить тип в своей лямбде перед вызовом run метода:

class Foo
class Bar

fun Foo.run() = "Foo.run"
fun Bar.run() = "Bar.run"

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar())
    val y = x.map({ a ->
        when (a) {
            is Foo -> a.run()
            is Bar -> a.run()
            else -> {
                /* ignore */
            }
        }
    })
    println("Hello, world!")
}

или

class Foo
class Bar

fun Foo.run() = "Foo.run"
fun Bar.run() = "Bar.run"

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar())
    val y = x.map({ a ->(a as? Foo)?.run() ?: (a as? Bar)?.run() })
    println("Hello, world!")
}

Поскольку выводимый тип элементов в x равен Any, он может содержать элементы, отличные от Foo и Bar, поэтому ваш код не найдет метод для такого элемента, и это проверяется при компиляции время.

И даже если вы добавите функцию расширения с проверкой типа reified, она будет преобразована в наименьший общий родительский элемент:

class Foo
class Bar

fun Foo.run() = "Foo.run"
fun Bar.run() = "Bar.run"
fun Any.run() = "Any.run" // this is type erasured version of 'inline fun <reified T> T.run() = T::class.simpleName+".run"'

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar())
    val y = x.map(Any::run)
    println(y)
}

напечатает [Any.run, Any.run]

Но используя ссылку на this, вы можете предоставить данные для вашей функции, например:

class Foo 
class Bar 

inline fun Any.run() = this::class.simpleName + ".run"

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar())
    val y = x.map(Any::run)
    println(y)
}

напечатает [Foo.run, Bar.run]

Вы также можете переместить отправляющий код тип дочернего класса в функцию расширения, например:

class Foo
class Bar

fun Foo.run() = "Foo!!!.run"
fun Bar.run() = "Bar!!!.run"
fun Any.run() = when(this){
    is Foo -> run()
    is Bar -> run()
    else -> { "Unknown.run" }
}

fun main(args: Array<String>) {
    val x = listOf(Foo(), Bar(), Any())
    val y = x.map({ a -> a.run()
    })
    println(y)
}

напечатает [Foo!!!.run, Bar!!!.run, Unknown.run]

0 голосов
/ 07 мая 2018

Вы можете сделать это.

fun <T> T.run() = "run"

Это определение run должно сделать остальную часть вашего кода компилируемой.

Если вы хотите ограничить количество вызываемых абонентов run, вы должны создать общий sealed class, подобный этому.

class Foo : Bla()
class Bar : Bla()
sealed class Bla

fun <T : Bla>.run() = "run"

// this works, too
// fun Bla.run() = "run"

Таким образом, только экземпляры Foo и Bar могут использовать run.

Но на вашем месте я бы сделал run функцией-членом Bla.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...