3

我是 Arrow 的新手,并试图建立我的心智模型来了解它的效果系统是如何工作的;特别是它如何利用 Kotlin 的suspend系统。我非常模糊的理解如下;如果有人可以确认,澄清或更正它,那就太好了:

因为 Kotlin 不支持更高级的类型,所以将 applicatives 和 monads 实现为类型类是很麻烦的。相反,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)
}

在 Haskell 的 do-notation 中,这将是

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)

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

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

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

4

2 回答 2

5

舒斯特,

你说得对,Arrow 使用 Kotlin 的暂停功能来编码像 monad comphrensions 这样的东西。

要回答您的第一个问题:

Kotlin 拥有suspend的语言(和 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)
}

回答你的第二个问题: IO因为 Kotlin 中不需要数据类型,因为它suspend可以IO像在Haskell. 编译器还在运行时做了很多优化,就像Haskellfor一样IO

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

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

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

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

于 2022-01-31T08:59:08.280 回答
1

我想我不能回答你问的所有问题,但我会尽我所能回答我知道如何回答的部分。

在 Arrow 中实现非 IO monad 理解而不使所有函数都变成挂起函数的推荐方法是什么?或者这实际上是要走的路?

您可以将nullable.eagereither.eager分别用于纯代码。使用nullable/either(without .eager) 允许您在内部调用挂起函数。Usingeager意味着你只能调用非挂起函数。(并非所有 kotlin 中的有效函数都标记为挂起)

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

您可以使用扩展函数来模拟 Reader。例如:

suspend fun <R> R.doSomething(i: Int): Either<Error, String> = TODO()

结合Reader+ IO+ Either。您可以从 Arrow 维护者 Simon那里找到一个更大的示例。

于 2022-01-31T08:46:12.867 回答