Monads如何被认为是纯粹的?

abh*_*kdm 6 monads haskell functional-programming purely-functional

我对Haskell非常陌生,对语言的"架构"印象深刻,但是我仍然困扰着monad是如何纯粹的.

由于你有任何指令序列,它使它成为一个不纯的函数,尤其是 I/O函数从任何角度来看都不是纯粹的.

是因为Haskell像所有纯函数一样假设IO函数也有返回值,但是以操作码的形式还是什么?我真的很困惑.

red*_*neb 20

想到这一点的一种方法是,类型的值IO a是"配方",包含如果执行将具有副作用的指令列表.然而,构建"食谱"没有任何副作用.因此,haskell程序(其类型IO ())基本上是构造这样的配方的计算.重要的是,程序不执行配方中的任何指令.配方完成后,程序终止.然后编译器(或解释器)获取该配方并执行它.但是程序员编写的代码不再运行,因此配方中的指令在程序范围之外执行.


Eri*_*ikR 10

"monad可以纯粹"的一种方式是它们可以代表纯粹的表达.即List List monad:

do x <- xs
   return (x+1)
Run Code Online (Sandbox Code Playgroud)

是一样的map (+1) xs.

你可以说"monad可以是纯粹的"的另一种方式是Haskell区分创建 monadic计算和运行计算.

创建monadic计算是纯粹的,不涉及任何副作用.例如,replicateM 3 foo将执行foo三次replicateM 3 (putStrLn "Hello, world").纯度允许我们推理与之replicateM 3 foo相同do { foo; foo; foo }.请注意,无论何种计算foo都是如此 - 它可能是纯计算或涉及某种效果的计算.

只有在运行 monadic计算时才会出现副作用.在IO monad的情况下,这在执行时的运行时发生main.其他monad有自己的"run"函数,即runReaderReader monad,runStateState monad等.


Car*_*arl 9

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

"Monad"指的是可以在具有某些较高通道类型构造函数的类型上实现的特定组合模式.整个概念与一对操作的类型以及这些操作必须如何与自己和彼此交互的规则联系在一起.

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

概念只是没有相关性.

现在,所有这一切,我明白你来自哪里.几乎所有关于Haskell的文章都使用"IO monad"或"使用monads来控制效果"或其他类似的东西.问题是,每次使用这样的术语都会产生误导.

IO是一种类型.这与不纯的语言有什么不同.IO操作是特定类型的.这就是让Haskell在与外部世界互动时保持原则(在某些方面)保持原则的原因.构建特定类型的值以描述与外部世界的交互.这些值是纯粹的,如其他答案中所述.

那么Monad在哪里适合整个事情?嗯,IO需要将值组合在一起,以便从更简单的操作中构建更复杂的IO操作.事实证明,他们将它们组合在一起的方式正是Monad界面描述的那种组合.但是将列表与值组合flatMap或使用Option值也是如此andThen.

强调Monad重要,特殊或有趣的东西会损害Haskell的声誉及其对初学者的可接受性.特别是因为它不重要,特别或有趣.我能做的最好的比较是IteratorJava.是的,这种语言对于使用Monad/ 有一些语法糖Iterator.如果您事先不了解这个概念,或者涉及到进入某个超级秘密的启蒙社会所必需的深层含义,那么这并不意味着语言是无法接近的.归根结底,这两个想法都不是很深刻或令人惊讶.它们只是非常广泛适用,简单的想法,当你手头有一些语法糖时更容易使用.


Wil*_*ess 5

redbneb回答几乎是正确的,只是对于Monads 来说,这两个时间线是混合在一起的,这是它们的本质;

Haskell 计算确实发生在外部世界提供了一些输入之后,例如,在之前的计算步骤中;构建下一个配方?“计算描述”,然后依次运行。否则它不会是一个 Monad,而是一个Applicative,它构建了它的配方?来自提前已知的组件的描述。

而低级的Functor本身已经有两个时间线(这是它的本质):IO avalue 描述了一个“外部世界”的 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 ]
Run Code Online (Sandbox Code Playgroud)

(用 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]                  
Run Code Online (Sandbox Code Playgroud)

本质上,一元计算是由无法在组合计算的运行时间之前构建的步骤构建的,因为要构建的配方由先前计算的值所产生的值决定- 值,由配方的计算产生实际执行。

下图也可能有启发性

管道图像