我对Haskell非常陌生,并且对该语言的“架构”印象深刻,但仍然困扰我 monads 如何是纯粹的。
由于你有任何指令序列,它使它成为一个不纯的函数,特别是具有I / O的函数从任何角度来看都不是纯粹的。< / p>
是否因为Haskell像所有纯函数一样假设IO
函数也有返回值,但是以操作码或其他形式存在?我真的很困惑。
答案 0 :(得分:20)
考虑到这一点的一种方法是类型IO a
的值是&#34;配方&#34;,其中包含如果执行会产生副作用的指令列表。构建&#34;配方&#34;但是,没有任何副作用。因此,haskell程序(其类型为IO ()
)基本上是构造这样一个配方的计算。重要的是,程序不执行配方中的任何指令。配方完成后,程序终止。然后编译器(或解释器)获取该配方并执行它。但程序员编写的代码不再运行,因此配方中的指令在程序范围之外执行。
答案 1 :(得分:10)
&#34; monads可以纯粹的一种方式&#34;是他们可以代表纯粹的表达。即在List monad中:
do x <- xs
return (x+1)
与map (+1) xs
相同。
你可以说另一种方式&#34; monad可以是纯粹的&#34;是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有自己的&#34; run&#34;函数,即Reader monad的runReader
,State monad的runState
等。
答案 2 :(得分:9)
Monad不被认为是纯粹的或不纯的。他们完全不相关的概念。你的标题有点像问动词是如何被认为是美味的。
&#34;单子&#34; “特定模式”是指可以在具有某些高级类型构造函数的类型上实现的特定组合模式。整个概念与几个操作的类型以及这些操作必须如何与自己和彼此交互的规则联系在一起。
很少有语言可以有效地表达这个概念,因为它是如此抽象。除了Haskell之外唯一相对常见的语言是Scala。它在Scala中实际上相对常见,但由于某种原因它们称之为flatMap
。不出所料,在Scala中支持flatMap
的某些类型并不纯粹。他们正确地支持flatMap
100%,并且他们不纯洁。
概念不相关。
现在,所有这一切,我明白你来自哪里。几乎所有关于Haskell的文章都使用了像&#34; IO monad&#34;或者&#34;使用monads来控制效果&#34;或其他类似的事情。问题是,每次使用这样的术语都会产生误导。
IO
是一种类型。这与不纯的语言有什么不同。 IO操作是特定类型的值。这使得Haskell能够保持原则(在某些方面)保持原则,即使在与外部世界互动时也是如此。构建特定类型的值以描述与外部世界的交互。这些值是纯粹的,如其他答案中所述。
那么Monad在哪里适合整个事情?那么,IO
值需要组合在一起,以便从更简单的IO动作中构建更复杂的IO动作。事实证明,他们将它们组合在一起的方式正是Monad
接口所描述的那种组合。但是,将flatMap
或Option
值与andThen
组合在一起的列表也是如此。
强调Monad
作为重要,特殊或有趣的东西会损害Haskell的声誉及其对初学者的可接受性。特别是因为它不重要,特别或有趣。我可以做的最好的比较是Java中的Iterator
。是的,该语言对于使用Monad
/ Iterator
有一些语法上的含糖量。如果您事先不了解这个概念,或者涉及到进入某个超级秘密的启蒙社会所必需的深层含义,那么这并不意味着语言是无法接近的。归根结底,这两个想法都不是很深刻或令人惊讶。它们只是非常广泛适用,简单的想法,当你手头有一些语法糖时更容易使用。
答案 3 :(得分:2)
redbneb的answer几乎是正确的,除了对于 Monads ,两个时间轴是混合,这是他们的< / em>本质;
Haskell计算 在外部世界提供了一些输入之后发生,比如说,在之前的计算步骤中;构建 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 comprehensions编写)。当然,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]
基本上monadicic计算由步骤组成,这些步骤不能在组合计算的运行时之前构建,因为构造的配方由先前产生的值决定计算值 - 值,由配方在实际执行时的计算产生。
盯着下面的图片也可能会证明illuminating:
答案 4 :(得分:0)