Arrow 挂起函数和 monad 理解之间的关系

Ulr*_*ter 5 suspend kotlin arrow-kt

我是《绿箭侠》的新手,并尝试建立其效果系统如何工作的思维模型;特别是它如何利用 Kotlin 的suspend系统。我非常模糊的理解如下;如果有人可以确认、澄清或纠正它,那就太好了:

由于 Kotlin 不支持更高种类的类型,因此将应用程序和 monad 实现为类型类非常麻烦。相反,Arrow 从 Kotlin 的挂起机制提供的延续原语中派生出所有 Arrow 的单子类型的单子功能(绑定和返回)。这是正确的吗?特别是,短路行为(例如, fornullableeither)以某种方式实现为定界延续。我不太明白 Kotlin 挂起机制的哪个特定功能在这里发挥作用。

如果上述内容大致正确,那么我有两个后续问题:我应该如何包含非 IO 单子操作的范围?举一个简单的对象构造和验证示例:

suspend fun mkMessage(msgType: String, appRef: String, pId: String): Message? = nullable {
    val type = MessageType.mkMessageType(msgType).bind()
    val ref = ApplRefe.mkAppRef((appRef)).bind()
    val id = Id.mkId(pId).bind()
    Message(type, ref, id)
}
Run Code Online (Sandbox Code Playgroud)

在 Haskell 的 do 表示法中,这将是

mkMessage :: String -> String -> String -> Maybe Message
mkMessage msgType appRef pId = do
    type <- mkMessageType msgType
    ref <- mkAppRef appRef
    id <- mkId pId
    return (Message type ref id)
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,函数都会返回 monad 类型(可以为 null 的值,或者可能)。然而,虽然我可以在任何我认为合适的地方使用 Haskell 中的纯函数,但 Kotlin 中的挂起函数只能从挂起函数中调用。通过这种方式,Arrow 中简单的非 IO monad 理解的行为就像一个 IO monad,必须在我的代码库中进行线程化;我想这是因为挂起机制是为实际 IO 操作设计的。在 Arrow 中实现非 IO monad 理解而不将所有函数都变成挂起函数的推荐方法是什么?或者这实际上是要走的路吗?

第二:如果除了非 IO monad(可空、读取器等)之外,我还想要 IO - 比如读取文件并解析它 - 我将如何结合这两种效果?是否有多个挂起作用域对应于所涉及的不同 monad,并且我需要以某种方式嵌套这些作用域,就像我在 Haskell 中堆叠 monad 转换器一样?

上面的两个问题可能意味着我仍然缺乏一种心理模型,可以在 Kotlin 挂起机制之上的基于连续的实现与 Haskell 中的通用 monad-as-typeclass 实现之间架起桥梁。

nom*_*Rev 8

舒斯特尔,

你是对的,Arrow 使用 Kotlin 的悬挂功能来编码诸如 monad comphrensions 之类的东西。

回答你的第一个问题:

Kotlinsuspend语言(和 Kotlin Std)中都有,默认情况下suspend只能从其他suspend代码中调用。然而,编译器还有一个名为RestrictsSuspension 的功能,它不允许混合suspend范围,因此不允许组合的能力IOEither例如。我们公开了一个辅助 DSL,either.eager它使用 进行编码RestrictsSuspension,并且不允许调用外部挂起函数。

这允许您进行编码mkMessage :: String -> String -> String -> Maybe Message

fun mkMessage(msgType: String, appRef: String, pId: String): Message? = nullable.eager {
    val type = MessageType.mkMessageType(msgType).bind()
    val ref = ApplRefe.mkAppRef((appRef)).bind()
    val id = Id.mkId(pId).bind()
    Message(type, ref, id)
}
Run Code Online (Sandbox Code Playgroud)

回答你的第二个问题: IOKotlin 中不需要数据类型,因为suspend可以IO像在Haskell. 编译器还在运行时进行了大量优化,Haskell就像IO.

所以签名suspend fun example(): Either<Error, Value>相当于EitherT IO Error ValueHaskell 中的签名。然而,这些IO操作不是在 Kotlin Std 中实现的,而是在KotlinX Coroutines库中实现的,并且Arrow Fx Coroutines还提供了一些数据类型和更高级别的操作,例如parTraverse在 KotlinX Coroutines 之上定义的操作。

它与 Haskell 中略有不同,因为我们可以混合效果而不是使用 monad 转换器堆叠它们。这意味着我们可以IOEither操作内部调用操作。这是由于编译器可以在悬挂系统中进行特殊功能和优化。这篇博客解释了这种优化是如何工作的,以及它为何如此强大。https://nomisrev.github.io/inline-and-suspend/

这里还有一些关于 Kotlin 中的 Continuations 和无标签编码的更多背景知识。https://nomisrev.github.io/continuation-monad-in-kotlin/

我希望这能完全回答你的问题。