【问题标题】:Workarounds for lack of HKT for the specific case F<Nothing> for an F<out X>特定情况下缺少 HKT 的解决方法 F<Nothing> for an F<out X>
【发布时间】:2019-11-26 17:07:24
【问题描述】:

在 Kotlin 中是否有任何方法可以将 Foo 与 Bar 分开,在以下最小示例中留下 defaultBar 抽象?

sealed class Bar<out X> {
  data class Hello<X>(val who: X): Bar<X>()
  object Empty : Bar<Nothing>()
}

interface Foo {
  fun defaultBar(): Bar<Nothing> = Bar.Empty
  fun <P> foo(bar: Bar<P> = defaultBar()): Bar<P> = bar
}

fun main(args: Array<String>) {
  println((object : Foo{}).foo(Bar.Hello("world")))
}

“分开”是指我希望Foo 和Bar 位于两个完全独立的文件中,因此它们不会相互提及。方法foo 应该是“硬”的,它应该保留在Foo 中。 defaultBar 方法应该是“微不足道的”,它可以在任何地方实现。我想同时提到Foo 和Bar 的唯一地方应该是提供defaultBar 的具体实现的某个单独的类。


我尝试过的

最初的尝试似乎很有希望:

sealed class Bar<out X> {
  data class Hello<X>(val who: X): Bar<X>()
  object Empty : Bar<Nothing>()
}

interface Foo<B>{
  fun <P: B> defaultB(): P
  fun <P: B> foo(bar: P = defaultB()): P = bar
}

object FooImpl : Foo<Bar<Any>> {
  override fun <P: Bar<Any>> defaultB(): P = Bar.Empty
}

fun main(args: Array<String>) {
  println(FooImpl.foo(Bar.Hello("world")))
}

不幸的是,Kotlin 不能从 Bar&lt;out X&gt; 和 P subtypeOf Bar&lt;Any&gt; 派生出 Bar&lt;Nothing&gt; subtypeOf P。

而且,我也不会写类似的东西

sealed class Bar<out X> {
  data class Hello<X>(val who: X): Bar<X>()
  object Empty : Bar<Nothing>()
}

interface Foo<B, N: B> {
  fun defaultBar(): N
  fun <P : B super N> foo(bar: Bar<P> = defaultBar()): Bar<P> = bar
}

object FooImpl : Foo<Bar<Any>, Bar<Nothing>> {
  fun defaultBar(): Bar<Nothing> = Bar.Empty
}

fun main(args: Array<String>) {
  println(FooImpl.foo(Bar.Hello("world")))
}

,因为除了不支持 HKT 之外,Kotlin 也不支持使用站点的下限类型。


(可选)我希望是可能的

以下 Scala sn-p 说明了我的意思:

sealed trait Bar[+X]
case class Hello(who: String) extends Bar[String]
case object Empty extends Bar[Nothing]

trait Foo {
  val defaultBar: Bar[Nothing] = Empty
  def foo[P](bar: Bar[P] = defaultBar): Bar[P] = bar
}

println((new Foo{}).foo(Hello("world")))

这可以重构为:

import language.higherKinds

sealed trait Bar[+X]
case class Hello(who: String) extends Bar[String]
case object Empty extends Bar[Nothing]

trait Foo[B[+_]] {
  val defaultB: B[Nothing]
  def foo[P](b: B[P] = defaultB): B[P] = b
}

object FooImpl extends Foo[Bar] {
  val defaultB: Bar[Nothing] = Empty
}

println(FooImpl.foo(Hello("world")))

所以Foo 不会在任何地方提及Bar。


(可选)为什么我希望它应该是可能的

对于这种特殊情况,不需要完整的 HKT,因为我们只需要在单一类型 Nothing 上评估 Bar:

sealed trait Bar[+X]
case class Hello(who: String) extends Bar[String]
case object Empty extends Bar[Nothing]

trait Foo[N] {
  val defaultBar: N
  def foo[P >: N](bar: P = defaultBar): P = bar
}

object FooInstance extends Foo[Bar[Nothing]] {
  val defaultBar = Empty
}

println(FooInstance.foo(Hello("world")))

这在没有更高种类的情况下有效,Java 的 super 就足够了。但是,正如上面已经提到的,Kotlin 在使用站点似乎没有像 &gt;: / super / lower type bounds 这样的东西。


问题

除了将foo 移动到FooImpl,即将Foo 和FooImpl 合并到一个大文件中,还有什么可以做的吗?

【问题讨论】:

    标签: generics kotlin types higher-kinded-types


    【解决方案1】:

    您可以通过制作foo扩展函数并编写B : P而不是P super B来模仿super:

    sealed class Bar<out X> {
        data class Hello<out X>(val who: X) : Bar<X>()
        object Empty : Bar<Nothing>()
    }
    
    interface Foo<out B> {
        fun defaultBar(): B
    }
    
    fun <B : P, P> Foo<B>.foo(bar: P = defaultBar()): P = bar
    
    object FooImpl : Foo<Bar<Nothing>> {
        override fun defaultBar(): Bar<Nothing> = Bar.Empty
    }
    
    fun main() {
        println(FooImpl.foo(Bar.Hello("world")))
    }
    

    【讨论】:

    • 如果FooImpl实际上是一些其他接口并且我们想将foo暴露给实现类,这也有效:为此,我们只需将fun &lt;B&gt; Foo&lt;B&gt;.foo(bar: B = defaultBar()): B = bar移动到Foo。非常感谢!
    猜你喜欢
    • 2016-05-27
    • 2015-04-05
    • 1970-01-01
    • 1970-01-01
    • 2020-09-26
    • 2013-01-03
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多