【问题标题】:The Reader Monad in Kotlin with arrow-ktKotlin 中的 Reader Monad 带箭头 kt
【发布时间】:2022-10-19 22:32:15
【问题描述】:

我在一个中型 Kotlin 项目上工作,我需要通过许多纯函数的嵌套调用从文件中读取配置信息。对于 Reader monad 来说,这似乎是一个明显的例子。但是,我还没有弄清楚如何在 Kotlin 中有效地实现 Reader。

我正在使用 Arrow 库 (v1.1.3),但是 - 令我惊讶的是 - 它没有附带 Reader 的实现。使用 Arrow 通过函数调用线程化配置数据的首选方法是什么?由于 Arrow 已经转向使用 Kotlin 的本机挂起系统来理解 monad,我认为这意味着不需要专门的 Reader 实现。该怎么做呢?

【问题讨论】:

    标签: kotlin monads arrow-kt reader-monad


    【解决方案1】:

    Arrow 曾经让 reader monad 被淘汰,但从那时起我们就停止支持这种包装器,转而支持 Kotlin 惯用模式。

    在 Kotlin 中有几种方法可以解决这个问题,但最有希望的是 context receivers

    具体例子可以看here,小视频教程here

    然而,这在 Kotlin 中还不稳定,目前仅可用于 JVM。有一种方法可以使用扩展函数来解决相同的问题,但它目前需要更多样板文件。在哪里扩展泛型类型 R (Reader),并将 R 约束到所需的实例。

    suspend fun <R> R.getProcessUsers(/* add any arguments as needed */): Either<ProcessingError, List<ProcessedUser>>
      where R : Repo,
            R : Persistence =
      fetchUsers().process()
    

    然后最终调用这个函数,你需要使R具体化,你通过使RepoPersistence和接口来做到这一点,然后你可以使用委托。

    class DataModule(
      persistence: Persistence,
      repo: Repo
    ) : Persistence by persistence, Repo by repo
    
    suspend fun main(): Unit {
      // This is your router { get { } } router definition or
      // your Android launch { } or compose function.
    
      // Generic top-level function automatically got enabled
      val processedUsers = DataModule(MockPersistence(), MockRepo()).getProcessUsers()
      println(processedUsers)
    
      // Call the alternative approach
      val processedUsers2 = DataModule2(MockPersistence(), MockRepo()).getProcessUsers2()
      println(processedUsers2)
    }
    

    但是仍然可以实现Reader,但它可能应该是实现suspend 版本的ReaderT 变体。

    编辑:

    suspend 的实现支持Reader 与类似于 Arrow 的 DSL 可以这样实现:

    public class Reader<R, A>(public val reader: suspend (R) -> A) {
      
      public companion object {
        public fun <R> ask(): Reader<R, R> = Reader { it }
      }
      
      public fun <T> local(f: (T) -> R): Reader<T, A> = Reader { r: T -> reader(f(r)) }
    }
    
    public interface ReaderEffect<R> {
      public suspend fun <A> Reader<R, A>.bind(): A
    }
    
    public fun <R, A> reader(action: suspend ReaderEffect<R>.(R) -> A): Reader<R, A> =
      Reader { r ->
        val effect = object : ReaderEffect<R> {
          override suspend fun <A> Reader<R, A>.bind(): A = reader(r)
        }
        action(effect, r)
      }
    
    public val one: Reader<String, Int> = reader { input -> input.toInt() }
    public val sum: Reader<String, Int> = reader { one.bind() + one.bind() }
    
    public suspend fun main(): Unit {
      val res = sum.reader.invoke("1")
      println(res) // 2
    }
    

    【讨论】:

    • 感谢您的详细回答。我仍然卡住了——通过扩展函数的解决方案,我仍然需要将我的配置对象(在这种情况下为 DataModule 的实例)传递到我实际调用它的地方,这可能是调用堆栈深处的几层.我看不出这里的解决方案如何帮助我在没有显式参数传递的情况下通过调用堆栈线程化环境。
    • 我为支持suspend 的Reader 添加了一个实现,以及一个类似于Arrow 的DSL。我希望这有助于@UlrichSchuster。我保留了其余的答案,因为上下文接收器将是 Kotlin 中这种模式的未来。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-18
    • 2017-08-16
    • 1970-01-01
    • 2018-03-26
    • 1970-01-01
    • 2018-04-09
    相关资源
    最近更新 更多