Monads如何被认为是纯的?

How Monads are considered pure?

我是 Haskell 的新手,对该语言的 "architecture" 印象深刻,但仍然困扰着我 monads可以纯

由于您有 任何 指令序列,它使它成为一个不纯的函数,尤其是 具有 I/O 的函数不会'无论从哪个角度看都不纯粹

是否因为 Haskell 假设,像所有纯函数一样,IO 函数也有一个 return 值,但以操作码或其他形式存在?我真的很困惑。

"monads can be pure" 的一种方式是它们可以表示纯表达式。 IE。在 List monad 中:

do x <- xs
   return (x+1)

map (+1) xs相同。

你可以说 "monads can be pure" 的另一种方式是 Haskell 区分 creating monadic computation 和 运行ning 计算。

创建单子计算是纯粹的,不涉及任何副作用。例如,replicateM 3 foo 将执行 foo 三次 如 replicateM 3 (putStrLn "Hello, world")。纯洁使我们能够推理 replicateM 3 foodo { foo; foo; foo } 相同。请注意,无论 foo 是哪种计算,这都是正确的 - 它可以是纯粹的计算,也可以是涉及某种效果的计算。

只有当您 运行 单子计算时才会发生副作用。在 IO monad 的情况下,这发生在执行 main 的 运行 时间。 其他 monad 有它们自己的 "run" 函数,即 runReader 用于 Reader monad,runState 用于 State monad,等等

一种思考方式是 IO a 类型的值是一个 "recipe",包含一个指令列表,如果执行这些指令会产生副作用。但是,构造 "recipe" 没有任何副作用。所以一个 haskell 程序(其类型是 IO ())基本上是一个构建这样一个食谱的计算。重要的是,该程序不执行配方中的任何指令。配方完成后,程序终止。然后编译器(或解释器)获取该配方并执行它。但是程序员写的代码已经不是运行了,所以菜谱中的指令是在程序范围之外执行的

单子不被认为是纯的或不纯的。它们是完全不相关的概念。你的标题有点像在问动词如何被认为是美味的。

"Monad" 指的是一种特定的组合模式,可以在具有某些 higher-kinded 类型构造函数的类型上实现。整个概念与一对操作的类型以及这些操作必须如何相互交互的规则密切相关。

很少有语言可以有效地表达这个概念,因为它太抽象了。除了 Haskell 之外,唯一相对通用的语言是 Scala。它实际上在 Scala 中也相对常见,尽管出于某种原因他们称之为 flatMap。不出所料,Scala 中某些支持 flatMap 的类型不是纯类型。他们支持 flatMap 100% 正确,而且他们并不纯粹。

只是概念不相关。

现在,说了这么多,我明白你的意思了。您在 Haskell 上看到的几乎每篇文章都使用 "the IO monad" 或 "uses monads to control effects" 或其他类似词组。问题是,每次使用这样的术语都会产生严重的误导。

IO 是一种类型。这就是与不纯语言的不同之处。 IO 操作是特定类型的。这就是 Haskell 能够(在某些方面)保持纯洁原则的原因,即使在与外界互动时也是如此。构建特定类型的值是为了描述与外界的交互。如其他答案中所述,这些值是纯值。

那么 Monad 在整个过程中处于什么位置呢?好吧,IO 值需要组合在一起以从更简单的 IO 操作构建更复杂的 IO 操作。事实证明,它们组合在一起的方式正是 Monad 界面描述的那种组合。但将列表与 flatMapOption 值与 andThen.

组合也是如此

Monad 强调为重要、特殊或有趣的事物会损害 Haskell 的声誉及其对初学者的易用性。特别是因为它不重要,不特别或有趣。我能做的最好的比较是 Java 中的 Iterator。是的,该语言有一些语法糖来处理 Monad/Iterator。不,这并不意味着如果您事先不知道这个概念,那么这门语言是无法接近的,或者涉及到进入某个 super-secret 启蒙社会所必需的深层含义。归根结底,这两个想法都不是很深刻或令人惊奇。它们的适用范围非常广泛,想法简单,当您手头有一点语法糖时,这些想法更容易使用。

redbneb's 几乎是对的,除了 Monads 两条时间线是 intermingled,也就是 他们的本质;

a Haskell 计算 does 发生在外部世界提供了一些输入之后,比如说,在之前的计算步骤中;构建 next 配方⁄“计算描述”,然后依次是 运行。否则它就不会是一个 Monad,而是一个 Applicative,它根据提前已知的组件构造它的配方/描述。

而低级的Functor本身已经有了两条时间线(也就是的本质):IO a值描述了一个“外部世界的“IO 计算”产生“内部”⁄ 纯 a 结果。

考虑:

[f x | x <- xs]             f <$> xs    Functor       [r |      x<-xs,r<-[f x]]
[y x | y <- f,  x <- xs]    f <*> xs    Applicative   [r | y<-f,x<-xs,r<-[y x]]
[r   | x <- xs, r <- f x]   f =<< xs    Monad         [r |      x<-xs,r<- f x ]

(用 monad 推导式编写)。当然 Functor (Applicative / Monad / ...) 也可以是纯的;那里仍然有两条时间线⁄“世界”。

几个具体的例子:

~> [x*2 | x<-[10,100]]            
~> [r   | x<-[10,100], r <- [x*2]]           -- non-monadic
[20,200]                                     -- (*2) <$> [10,100] 

~> [x*y | x<-[10,100], y <- [2,3]]          
~> [r   | x<-[10,100], y <- [2,3], r <- [x*y]]        -- non-monadic
[20,30,200,300]                                       -- (*) <$> [10,100] <*> [2,3]

~> [r | x<-[10,100], y <- [2,3], r <- replicate 2 (x*y) ]
~> [r | x<-[10,100], y <- [2,3], r <- [x*y, x*y]]        -- still non-monadic:
~> (\a b c-> a*b) <$> [10,100] <*> [2,3] <*> [(),()]     -- it's applicative!
[20,20,30,30,200,200,300,300]

~> [r | x<-[10,100], y <- [2,3], r <- [x*y, x+y]]                -- and even this
~> (\a b c-> c (a*b,a+b)) <$> [10,100] <*> [2,3] <*> [fst,snd]   -- as well
~> (\a b c-> c a b)       <$> [10,100] <*> [2,3] <*> [(*),(+)]     
[20,12,30,13,200,102,300,103]

~> [r | x<-[10,100], y <- [2,3], r <- replicate y (x*y) ]  -- only this is _essentially_
~> [10,100] >>= \x-> [2,3] >>= \y -> replicate y (x*y)     --    monadic !!!!
[20,20,30,30,30,200,200,300,300,300]                  

本质上,单子计算是由无法在组合计算的 运行-time 之前构建的步骤构建的,因为要构建的配方由先前计算得到的值值 -- 实际执行时由配方计算产生的值。

下图也可能证明:

添加一个答案只是因为我分享的内容对我有很大帮助。

最著名的 Monad 之一(如果不是的话)是 IO,它允许与“现实世界”进行交互。

不纯吗?

不,它是纯粹的:它接受“真实世界”并吐出两个输出,

  • 您将“有意”使用的那个,
  • 另一个,可能修改过的“真实世界”,它将坐在那里等待下一个单子动作将其输入。

一个更广泛的解释和一个非常清楚的例子是 here

this 是另一种有用的(但也很有趣!)在您的脑海中描绘仿函数、应用仿函数和 monand 的方式。