Параметры функции высшего порядка Kotlin: передача подтипов

У меня возникла проблема с параметрами функций в Kotlin. Я объясню проблему с помощью некоторого кода.

Я создал иерархию классов. Когда я передаю подтип в функцию, ожидающую родительский тип, проблем нет.

open class A (val i: Int)
class B (val j: Int) : A(j)

fun f(x: A){
    print(x)
}

fun test_f(){
    f(A(1))
    f(B(1)) //no problem
}

Я попытался имитировать это с помощью параметров функции.

fun g(x: (A)->Int){
    print(x)
}

fun test_g(){
    val l1 = { a: A -> a.hashCode()}
    g(l1)

    val l2 = { b: B -> b.hashCode()}
    g(l2) //Error: Type mismatch. Required: (A)->Int, Found: (B)->Int
}

Кажется, что тип функции (B) -> Int не является подтипом (A) -> Int. Каков наилучший способ решить эту проблему?

Моя изначальная проблема должен определить функцию более высокого порядка в A.h, которая принимает функцию z: (A) -> X в качестве параметра. И я хочу вызвать h объект типа B и передать функцию z: (B) -> X.

Обновлять: Я пробовал дженерики с верхней границей, но моя проблема не решена. Пожалуйста, найдите код ниже:

// Using generics doesn't allow me to pass A.
open class A (val i: Int) {
    fun <M: A> g(x: (M)->Int){
        print(x(this)) // Error: Type mismatch. Expected: M, Found: A 
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
0
801
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете решить эту проблему, используя дженерики и функция расширения на универсальном приемнике. Получение функции расширения из вашего обновленного образца:

fun <T : A> T.g(x: (T)->Int){
    print(x(this))
}

Таким образом гарантируется, что получатель и первый тип параметра данной функции совпадают, что является либо A, либо его подтипом.

Я пробовал это, но тогда это не позволяет мне пройти A. Подробности смотрите в обновлении.

Neo 08.04.2019 13:38

@Neo обновил ответ ... в основном показывая, как вы можете решить свой обновленный образец с помощью универсальной функции на универсальном приемнике ...

Roland 08.04.2019 14:26

Да, это именно то, что я искал! Аккуратный подход.

Neo 10.04.2019 07:55

Чтобы помочь другим взглянуть на это: важное различие между кодом, который я разместил в своем обновлении, и ответом @Roland - это часть "T.g", которая позволяет функции быть функцией расширения для универсального типа T.

Neo 10.04.2019 08:02

То, что вы пытаетесь сделать, — это преобразование из типа функции (B) -> Int (источник) в (A) -> Int (цель). Это не безопасное преобразование.

Ваша исходная функция (B) -> Int принимает любой экземпляр, который является B, но не обязательно экземпляр типа A. Более конкретно, он не может обрабатывать все аргументы типа A, но не типа B.

Представьте, что ваши классы выглядят так:

open class A
class B : A {
    fun onlyB() = 29
}

Вы можете определить функцию

val fb: (B) -> Int = { it.onlyB() }
val fa: (A) -> Int = fb // ERROR

Функция fb не сможет работать с классом A, так как A не имеет функции onlyB(). Как следствие, вам не разрешено преобразовывать его в тип функции, который принимает A параметры.


Эта концепция называется контравариантность, что означает, что входные параметры могут быть ограничены, только став более конкретно, а не более абстрактными. Итак, работает обратное направление:

val fa: (A) -> Int = { it.hashCode() }
val fb: (B) -> Int = fa // OK, every B is also an A

Напротив, для возвращаемых значений применяется концепция ковариация. Это означает, что возвращаемым значениям разрешено становиться более абстрактный, но не более конкретными:

val fInt: (B) -> Int = { it.onlyB() }
val fNum: (B) -> Number = fInt // OK, every Int is also a Number

Эти отношения можно использовать в универсальных классах, используя ключевые слова Kotlin in (контравариантность) и out (ковариантность) — подробное объяснение см. в здесь.

Отличный ответ, но есть ли способ избежать возможной путаницы между в разнобой и неизменность? (например, «в» дисперсии?)

gidds 08.04.2019 16:27

Я думаю, что лучшими терминами (также известными на других языках) являются ковариация/контравариантность (см. Википедия). Котлин говорит о вход/выход в контексте дисперсия, но не явно «в дисперсии/вне дисперсии». Я могу отредактировать сообщение соответствующим образом.

TheOperator 08.04.2019 16:35

Даже лучше! (Правда, теперь в последнем абзаце опечатка, которая сильно портит эффект… :)

gidds 08.04.2019 16:48

И, конечно же, определенные способы использования предотвращают любой тип дисперсии.

Paul Stelian 25.06.2021 01:01

Другие вопросы по теме