【问题标题】:Why does ArrowKt reccomend I implement my effect interface with an object instead of a function?为什么 ArrowKt 建议我使用对象而不是函数来实现我的效果接口?
【发布时间】:2022-07-29 19:32:48
【问题描述】:

根据docs,我应该用一个对象来实现一个效果。

fun interface JustEffect<A> : Effect<Just<A>> {
  suspend fun <B> Just<B>.bind(): B = value
}

object effect {
  operator fun <A> invoke(func: suspend JustEffect<*>.() -> A): Just<A> =
    Effect.restricted(eff = { JustEffect { it } }, f = func, just = { Just(it) })
}

这是本教程的一般指南。我很好奇是否有人知道他们为什么使用对象?我的具体用例如下:

我们已经有一个包装器对象,称为PoseidonRes,它可以是成功或错误。我们普遍使用它,并且不想到处切换到 Either 类型。话虽如此,这是我的自定义效果,以及我是如何实现的。

fun interface PoseidonResEffect<A> : Effect<PoseidonRes<A>> {
    suspend fun <T> PoseidonRes<T>.bind(): T = when (this) {
        is SuccessResponse -> this.response
        is ErrorResponse -> control().shift(this)
    }
}

fun <A> posRes(func: suspend PoseidonResEffect<A>.() -> PoseidonRes<A>): PoseidonRes<A> =
    Effect.restricted(
        eff = { PoseidonResEffect { it } },
        f = func,
        just = { it }
    )

主要区别在于,我将函数接口实现为函数,而不是调用对象。我真的很想知道为什么推荐一种方式,而这看起来非常好。我已经挖掘了文档,但找不到答案。如果它实际上在文档中,请 RTFM 我。

在呼叫站点看起来像

        posRes { 
            val myThing1 = thingThatsPoseidonResYielding().bind()
            val myThing2 = thingThatsPosiedonResYielding2().bind
            SuccessResponse(order.from(myThing1, myThing2))
        }

这两种实现看起来都一样。测试通过任何一种方式。这是怎么回事?

【问题讨论】:

    标签: android kotlin functional-programming monads arrow-kt


    【解决方案1】:

    我们最近发布了一个更新的 API,它以更方便的方式简化了此类抽象的构建。同时还提供更大的性能优势!

    这是Option 的示例。

    翻译到您的域:

    首先我们创建一个将Effect&lt;ErrorResponse, A&gt; 映射到自定义类型的函数。当您编写任何其他可能导致ErrorResponse 的程序/Effect 并且您想转换为您的自定义类型时,这很有用。

    public suspend fun <A> Effect<ErrorResponse, A>.toPoseidonRes(): PoseidonRes<A> =
      fold({ it }) { SuccessResponse(it) }
    

    接下来我们创建一些额外的 DSL 糖,以便您可以方便地使用自己的类型调用 bind

    @JvmInline
    public value class PoseidonResEffectScope(private val cont: EffectScope< ErrorResponse>) : EffectScope<ErrorResponse> {
      override suspend fun <B> shift(r: None): B =
        cont.shift(r)
    
      suspend fun <T> PoseidonRes<T>.bind(): T = when (this) {
        is SuccessResponse -> this.response
        is ErrorResponse -> shift(this)
      }
    
      public suspend fun ensure(value: Boolean): Unit =
        ensure(value) { ErrorResponse }
    }
    
    @OptIn(ExperimentalContracts::class)
    public suspend fun <B> PoseidonResEffectScope.ensureNotNull(value: B?): B {
      contract { returns() implies (value != null) }
      return ensureNotNull(value) { ErrorResponse }
    }
    

    最后我们创建了一个 DSL 函数,它启用了上面定义的附加 DSL 语法。

    suspend fun <A> posRes(
      block: suspend PoseidonResEffectScope.() -> A
    ): PoseidonRes<A> = effect<ErrorResponse, A> {
      block(PoseidonResEffectScope(this))
    }.toPoseidonRes()
    

    附加信息:

    使用上下文接收器(以及 Kotlin 中即将推出的功能),我们可以简化上述 2 个代码 sn-ps 一吨。

    context(EffectScope<ErrorResponse>)
    suspend fun <T> PoseidonRes<T>.bind(): T = when (this) {
      is SuccessResponse -> this.response
      is ErrorResponse -> shift(this)
    }
    
    suspend fun <A> posRes(
      block: suspend EffectScope<ErrorResponse>.() -> A
    ): PoseidonRes<A> =
      effect<ErrorResponse, A>(block).toPoseidonRes()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 2012-10-05
      • 2014-01-05
      相关资源
      最近更新 更多