Kleisli 类别中的 Monads 有什么特别之处?

-3 javascript monads haskell functional-programming

相关的问题是

  1. Monads 有什么特别之处?

  2. bind 可以由 fmap 和 join 组成,那么我们是否必须使用 monadic 函数 a -> mb?

在第一个问题中:

Monads 有什么特别之处?

monad 是一种数学结构,在(纯)函数式编程中大量使用,基本上是 Haskell。但是,还有许多其他数学结构可用,例如应用函子、强单子或幺半群。有些更具体,有些更通用。然而,单子更受欢迎。这是为什么?

回答问题的评论:

在我的记忆中,monads 是由 Wadler 普及的,当时没有繁琐的 CPS 进行 IO 和没有显式状态传递的解析的想法是巨大的卖点;这是一个非常激动人心的时刻。AFAIR,Haskell 没有做构造函数类,但是 Gofer(Hugs 之父)做了。Wadler 建议为 monad 重载列表理解,所以后来出现了 do 符号。一旦 IO 成为 monadic,monads 就成为初学者的大事,将它们巩固为一个重要的东西。Applicative 在你可以的时候更好,Arrows 更通用,但它们来得较晚,而且 IO 很难卖 monad。– AndrewC 2013 年 5 月 9 日 1:34

@Conal 的回答是:

我怀疑对这一特定类型类 ( Monad) 的过多关注主要是历史上的侥幸。人们经常将 与 联系IO起来Monad,尽管这两个是独立有用的想法(列表反转和香蕉也是如此)。因为IO是神奇的(有实现但没有外延)并且Monad通常与 相关联IO,所以很容易陷入神奇的思考中Monad

首先,我同意他们的观点,我认为Monads的用处主要来自Functors,我们可以在结构中嵌入许多函数,而Monads是通过joinM(M(X)) -> M(X)避免嵌套类型对函数组合的健壮性的一点扩展。

在第二个问题中:

我们是否必须使用 monadic 函数 a -> mb?

网络上的许多教程仍然坚持使用 monadic 函数,因为那是 Kleisli 三元组和 monad-laws。

和许多答案像

我喜欢将这样的 m 视为“计划到获取”,其中“计划”涉及纯计算之外的某种额外交互。

或者

在情况下Monad是没有必要的,它往往更容易使用ApplicativeFunctor或只是基本的纯函数。在这些情况下,这些东西应该(并且通常是)用来代替Monad. 例如:

ws <- getLine >>= return . words  -- Monad
ws <- words <$> getLine           -- Functor (much nicer)
Run Code Online (Sandbox Code Playgroud)

需要明确的是:如果没有 monad 也是可能的,并且没有 monad 更简单、更易读,那么你应该在没有 monad 的情况下做!如果 monad 使代码比它需要的更复杂或更令人困惑,请不要使用 monad!Haskell 拥有 monad 的唯一目的是使某些复杂的计算更简单、更易于阅读和更易于推理。如果没有发生这种情况,您不应该使用 monad

看了他们的回答,我想他们对 Monad 的特殊感受源于历史事件,Haskell 社区碰巧选择了 Kleisli 类别的 Monad 来解决他们的问题(IO 等)

所以,再一次,我认为 Monads 的用处主要来自 Functors,我们可以在结构中嵌入许多函数,而 Monads 是通过join:M(M(X)) -> M(X)避免嵌套类型对函数组合的稳健性的一点扩展。

事实上,在 JavaScript 中我实现如下..

函子

ws <- getLine >>= return . words  -- Monad
ws <- words <$> getLine           -- Functor (much nicer)
Run Code Online (Sandbox Code Playgroud)

关键是我们可以在 Functor 结构中实现我们喜欢的任何东西,在这种情况下,我只是将它设为 IO/console.log值。

还有一点是,要做到这一点,Monads 是绝对没有必要的。

单子

现在,基于上面的 Functor 实现,我添加了额外的join: MMX => MX功能来避免嵌套结构,这应该有助于复杂功能组合的稳健性。

功能与上面的 Functor 完全相同,请注意用法也与 Functor 相同fmap。这不需要“单子函数”来bind(单子的 Kleisli 组合)。

console.log("Functor");
{
  const unit = (val) => ({
    // contextValue: () => val,
    fmap: (f) => unit((() => {
      //you can do pretty much anything here
      const newVal = f(val);
    //  console.log(newVal); //IO in the functional context
      return newVal;
    })()),
  });

  const a = unit(3)
    .fmap(x => x * 2)  //6
    .fmap(x => x + 1); //7
}
Run Code Online (Sandbox Code Playgroud)

单子定律

以防万一,Monad 的这种实现满足 monad 定律,而上面的 Functor 不满足。

console.log("Monad");
{
  const unit = (val) => ({
    contextValue: () => val,
    bind: (f) => {
      //fmap value operation
      const result = (() => {
        //you can do pretty much anything here
        const newVal = f(val);
        console.log(newVal);
        return newVal;
      })();
      //join: MMX => MX
      return (result.contextValue !== undefined)//result is MX
        ? result //return MX
        : unit(result) //result is X, so re-wrap and return MX
    }
  });
  //the usage is identical to the Functor fmap.
  const a = unit(3)
    .bind(x => x * 2)  //6
    .bind(x => x + 1); //7
}
Run Code Online (Sandbox Code Playgroud)

所以,这是我的问题。

我可能是错的。

是否有任何反例表明 Functors 不能做 Monads 可以做的事情,除了通过扁平化嵌套结构来增强函数组合的健壮性?

Kleisli 类别中的 Monads 有什么特别之处?似乎很有可能通过一点扩展来实现 Monads,以避免 Functor 的嵌套结构,并且没有作为a -> m bKleisli 类别中实体的 monadic函数。

谢谢。

编辑(2018-11-01)

阅读答案,我同意console.log在应满足函子定律的 IdentityFunctor 内执行是不合适的,所以我像 Monad 代码一样注释掉了。

所以,消除这个问题,我的问题仍然成立:

是否有任何反例表明 Functors 不能做 Monads 可以做的事情,除了通过扁平化嵌套结构来增强函数组合的健壮性?

Kleisli 类别中的 Monads 有什么特别之处?似乎很有可能通过一点扩展来实现 Monads,以避免 Functor 的嵌套结构,并且没有作为a -> m bKleisli 类别中实体的 monadic函数。

@DarthFennec 的回答是:

“避免嵌套类型”实际上并不是 的目的join,它只是一个巧妙的副作用。你的方式让它听起来像是join剥离了外部类型,但 monad 的值没有改变。

我相信“避免嵌套类型”不仅仅是一个巧妙的副作用,而是在范畴论中对 Monad 的“连接”的定义,

monad 的乘法自然变换 ?:T?T?T 为每个对象 X 提供了一个态射?X:T(T(X))?T(X)

monad(在计算机科学中):与范畴论中的 monad 的关系

这正是我的代码所做的。

另一方面,

不是这种情况。join是 monad 的核心,它允许 monad做事

我知道以这种方式在Haskell很多人工具单子,但事实是,有可能仿函数在Haskell,不拥有join,或者有免费的单子join从第一名到定义的结构嵌入。它们是用户定义Functor 来做事的对象

所以,

您可以将函子看作基本上是一个容器。有一个任意的内部类型,围绕它的外部结构允许一些变化,一些额外的值来“装饰”你的内部值。fmap允许您以正常方式处理容器内的事物。这基本上是您可以用函子做的事情的极限。

monad 是一个具有特殊能力的函子: wherefmap允许您处理内部值,bind允许您以一致的方式组合外部值。这比简单的函子要强大得多。

这些观察不符合Maybe functor和Free monad存在的事实。

Dar*_*nec 5

是否有任何反例表明 Functors 不能做 Monads 可以做的事情,除了通过扁平化嵌套结构来增强函数组合的健壮性?

我认为这是重要的一点:

Monads 是通过 join 对函数组合的健壮性进行的一点扩展:M(M(X)) -> M(X)以避免嵌套类型。

“避免嵌套类型”实际上并不是 的目的join,它只是一个巧妙的副作用。你的方式让它听起来像是join剥离了外部类型,但 monad 的值没有改变。不是这种情况。join是 monad 的核心,它允许 monad做事

您可以将函子看作基本上是一个容器。有一个任意的内部类型,围绕它的外部结构允许一些变化,一些额外的值来“装饰”你的内部值。fmap允许您以正常方式处理容器内的事物。这基本上是您可以用函子做的事情的极限。

monad 是一个具有特殊能力的函子: wherefmap允许您处理内部值,bind允许您以一致的方式组合外部值。这比简单的函子要强大得多。


关键是我们可以在 Functor 结构中实现我们喜欢的任何东西,在这种情况下,我只是将它设为 IO/console.log值。

这实际上是不正确的。您能够在这里进行 IO 的唯一原因是因为您使用的是 Javascript,并且您可以在任何地方进行 IO。在像 Haskell 这样的纯函数式语言中,IO 不能在这样的函子中完成。

这是一个粗略的概括,但在大多数情况下,将其描述IO为美化的Statemonad是有用的。每个IO动作都需要一个额外的隐藏参数RealWorld(它代表现实世界的状态),可能从中读取或修改它,然后将其发送到下一个IO动作。该RealWorld参数贯穿整个链。如果某些东西被写入屏幕,它就会RealWorld被复制、修改和传递。但是“传递”是如何工作的呢?答案是join

假设我们想从用户那里读取一行,并将其打印回屏幕:

getLine :: IO String
putStrLn :: String -> IO ()

main :: IO ()
main = -- ?
Run Code Online (Sandbox Code Playgroud)

让我们假设IO是一个函子。我们如何实现这一点?

main :: IO (IO ())
main = fmap putStrLn getLine
Run Code Online (Sandbox Code Playgroud)

在这里,我们已经提升putStrLnIO,得到fmap putStrLn :: IO String -> IO (IO ())。如果你还记得,putStrLn接受 aString和 a hiddenRealWorld并返回一个 modified RealWorld,其中String参数被打印到屏幕上。我们已经用 提升了这个函数fmap,所以它现在接受 an IO(这是一个接受隐藏的动作RealWorld,并返回一个修改的RealWorld和一个String),并返回相同的 io 动作,只是包裹了一个不同的值(一个完全独立的动作这也需要一个单独的隐藏RealWorld并返回一个RealWorld)。即使在应用getLine到这个函数之后,也没有实际发生或被打印。

我们现在有一个main :: IO (IO ()). 这是一个带有 hidden 的动作RealWorld,并返回一个修改过的RealWorld和一个单独的动作。第二个操作采用不同的方法RealWorld并返回另一个修改过的RealWorld. 这本身是没有意义的,它不会给你任何东西,也不会在屏幕上打印任何东西。需要发生的是,两个IO动作需要连接在一起,以便一个动作的返回RealWorld作为另一个动作的RealWorld参数输入。这样它就变成了一个连续的RealWorlds链,随着时间的推移而发生变异。当两个IO动作与join.


当然,join根据你正在使用的 monad 做不同的事情,但是对于IOandState类型的 monad,这或多或少是幕后发生的事情。在很多情况下,您正在做一些不需要的非常简单的事情join,在这些情况下,很容易将 monad 视为函子或应用函子。但通常这还不够,在这些情况下我们使用 monad。


编辑:对评论和编辑问题的回应:

我没有看到分类理论中对 Monads 的任何定义解释了这一点。我读到的关于 join 的所有内容都是 stil,MMX => MX而这正是我的代码所做的。

你还能准确地说出函数的String -> String作用吗?它可能不会逐字返回输入、反转它、过滤它、附加到它、忽略它并返回一个完全不同的值,或者其他任何导致String? 类型不决定函数做什么,它限制函数可以做什么。由于join通常仅由其类型定义,因此任何特定的 monad 都可以执行该类型允许的任何操作。这可能只是剥离外层,也可能是将两层合二为一的某种极其复杂的方法。只要你从两层开始,到最后一层,都没有关系。该类型允许多种可能性,这是使 monad 开始变得如此强大的部分原因。

Haskell 中有 MaybeFunctor。那里没有“加入”或“绑定”,我想知道力量来自哪里。MaybeFunctor 和 MaybeMonad 有什么区别?

每个 monad 也是一个函子:monad 只不过是一个也有join函数的函子。如果您使用joinbindMaybe则将其用作 monad,并且它具有 monad 的全部功能。如果您不使用joinor bind,而只使用fmapand pure,那么您将它用作函子,并且它仅限于做函子可以做的事情。如果没有joinor bind,则没有额外的 monad 功能。

我相信“避免嵌套类型”不仅仅是一个巧妙的副作用,而是在范畴论中对 Monad 的“连接”的定义

的定义join是从嵌套 monad 到非嵌套 monad 的转换。同样,这可能意味着任何事情。说目的join是“避免嵌套类型”就像说目的+是避免数字对。大多数操作以某种方式组合事物,但很少有这些操作仅仅为了组合事物而存在。重要的是合并如何发生的

可能仿函数在Haskell,不拥有join,或者有免费的单子join从第一位置嵌入到定义的结构。它们是用户定义Functor 来做事的对象

我已经讨论过Maybe,当你只将它用作函子时,它是如何做不到你将它用作 monad 的。Free很奇怪,因为它是少数几个实际上什么都不做的monad 之一。

Free可用于将任何函子转换为 monad,这允许您使用do符号和其他便利。然而,自负Free是它join不会像其他 monad 那样组合你的动作,而是将它们分开,将它们插入到一个类似列表的结构中;这个想法是稍后处理此结构,并通过单独的代码组合操作。一种等效的方法是将处理代码移到join自身中,但这会将函子变成 monad,因此使用Free. 所以唯一有效的原因Free是因为它将 monad 的实际“做事”部分委托给其他地方;它join选择将操作推迟到在 monad 之外运行的代码。这就像一个+运算符,不是添加数字,而是返回抽象语法树;然后可以稍后以任何需要的方式处理该树。

这些观察不符合Maybe functor和Free monad存在的事实。

你是不正确的。如前所述,Maybe并且Free完全适合我以前的意见:

  • Maybe函子根本不具有相同的表现为Maybe单子。
  • Free单子变换函子成单子在它所能的唯一途径:通过不实施一元的行为,而不是简单地将它推迟到一些假设的处理代码。