为什么我们需要monads?
在我的愚见中,对于着名问题“什么是单子?”的回答,尤其是最被投票的问题,试图解释什么是单子,而没有明确解释为什么单子是真正必要的。 他们能解释为解决问题的方法吗?
为什么我们需要monads?
那么,我们有第一个大问题。 这是一个程序:
 f(x) = 2 * x 
 g(x,y) = x / y 
我们怎么说先执行什么? 我们如何使用不超过函数形成一个有序的函数序列(即程序 )?
  解决方案: 编写功能 。  如果你想先g然后f ,只需写f(g(x,y)) 。  这样,“程序”也是一个函数: main = f(g(x,y)) 。  好的但是 ... 
  更多的问题:一些函数可能会失败 (即g(2,0) ,除以0)。  我们在FP中没有“例外” (例外不是函数)。  我们如何解决它? 
  解决方案:我们让函数返回两种东西 :代替g : Real,Real -> Real (从两个实数到实数的函数),让我们让g : Real,Real -> Real | Nothing  g : Real,Real -> Real | Nothing (从两个实际功能(实际或没有))。 
但函数应该(更简单)只返回一件事 。
  解决方案:让我们创建一个新类型的数据来返回,一个“ 拳击类型 ”,可能包含一个真实或简直没有。  因此,我们可以有g : Real,Real -> Maybe Real 。  好的但是 ... 
  现在发生什么f(g(x,y)) ?  f并没有准备好消耗Maybe Real 。  而且,我们不想改变我们可以用g连接的每一个函数来使用Maybe Real 。 
解决方案:让我们有一个特殊的功能来“连接”/“撰写”/“链接”功能 。 这样,我们可以在幕后调整一个函数的输出来输入下面的函数。
  在我们的例子中: g >>= f (连接/合成g到f )。  我们希望>>=获得g的输出,检查它,并在情况下,它是Nothing只是不叫f并返回Nothing ;  或者相反,提取盒装的Real和饲料f 。  (这个算法仅仅是>>=对于Maybe类型的实现)。  另请注意, >>=必须每个“装箱类型”(不同的箱子,不同的适配算法)写入一次 。 
  许多其他问题都可以通过使用相同的模式来解决:1.使用“盒子”来编码/存储不同的含义/值,并且具有像g这样的函数来返回这些“盒装值”。  2.有一个作曲家/连接器g >>= f帮助将g的输出连接到f的输入,所以我们根本不需要改变任何f 。 
使用这种技术可以解决的显着问题是:
  具有全局状态,即函数序列中的每个函数(“程序”)都可以共享:解决方案StateMonad 。 
  我们不喜欢“不纯的功能”:对同一输入产生不同输出的功能。  因此,让我们标记这些函数,使它们返回一个标记/盒装值: IO monad。 
总幸福!
答案当然是, “我们不” 。 与所有抽象一样,这不是必需的。
  Haskell不需要monad抽象。  以纯语言执行IO不是必需的。  IO类型自己处理就好了。  现有的单块解除do块可以用GHC.Base模块中定义的bindIO , returnIO和failIO来GHC.Base 。  (这不是一个记录在hackage上的模块,所以我不得不指出它的源文件。)所以不,不需要monad抽象。 
所以如果不需要,它为什么存在? 因为发现许多计算模式形成一元结构。 抽象结构允许编写可以在该结构的所有实例中工作的代码。 简单地说 - 代码重用。
  在函数式语言中,为代码重用找到的最强大的工具就是函数的组合。  好的(.) :: (b -> c) -> (a -> b) -> (a -> c)运算符是非常强大的。  它可以轻松编写小函数,并以最少的语法或语义开销将它们粘合在一起。 
  但有些情况下这些类型工作不正确。  当你有foo :: (b -> Maybe c)和bar :: (a -> Maybe b) ? foo . bar  foo . bar不typecheck,因为b和Maybe b不是同一类型。 
  但是......几乎是正确的。  你只需要一点余地。  你希望能够将Maybe b当作基本b来对待。  不过,把它们作为同类型对待它是一个糟糕的主意。  这与托尼霍尔着名称为十亿美元的错误的空指针差不多。  所以如果你不能把它们当作同一类型,也许你可以找到一种扩展组合机制(.)规定的方法。 
  在这种情况下,真正研究(.)基础是非常重要的。  幸运的是,有人已经为我们做了这个。  事实证明, (.)和id的组合形成了一个称为类别的数学结构。  但是还有其他方式可以形成类别。  例如,Kleisli类别允许组成的对象稍微扩大一点。  一个Kleisli类的Maybe可能包含(.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)和id :: a -> Maybe a 。  也就是说,类别中的对象用一个Maybe增加(->) ,所以(a -> b)变成(a -> Maybe b) 。 
  突然之间,我们已经将构图的力量扩展到传统(.)操作无法解决的问题。  这是新抽象力量的来源。  Kleisli类别的工作类型不仅仅是Maybe 。  他们与每一种可以组合适当类别的类型合作,遵守类别法律。 
id . f  id . f = f f . id  f . id = f f . (g . h)  f . (g . h) = (f . g) . h (f . g) . h   只要你能证明你的类型服从这三个法则,你就可以把它变成一个Kleisli类别。  那有什么大不了的?  那么,事实证明monads和Kleisli类别完全一样。  Monad的return是一样的Kleisli id 。  Monad的(>>=)与Kleisli (.)不完全相同,但是从另一个角度来看,它们很容易编写。  当你将它们翻译为(>>=)和(.)之间的差异时,类别法则与monad法则相同。 
  那么为什么要经历这一切呢?  为什么要在语言中使用Monad抽象?  正如我上面提到的那样,它支持代码重用。  它甚至可以在两个不同的维度上重用代码。 
  代码重用的第一个维度直接来自抽象的存在。  您可以编写适用于所有抽象实例的代码。  整个monad-loop软件包由可与任何Monad实例一起工作的循环组成。 
  第二个维度是间接的,但是从构成的存在来看。  当组成很容易时,用小的可重用块编写代码是很自然的。  这与用函数的(.)运算符鼓励编写小的可重用函数是一样的。 
那么为什么抽象存在? 因为它被证明是一种工具,可以在代码中实现更多的组合,从而创建可重用的代码并鼓励创建更多可重用的代码。 代码重用是编程的圣杯之一。 单子抽象的存在是因为它让我们有点朝着这个圣杯前进。
本杰明皮尔斯在TAPL中表示
类型系统可以被认为是计算一种程序中术语运行时行为的静态近似。
这就是为什么配备了强大类型系统的语言严格地比表达性差,而不是类型差的语言。 你可以用同样的方式思考单子。
作为@Carl和sigfpe点,你可以装备一个数据类型的所有你想要的操作,而不诉诸monad,typeclasses或任何其他抽象的东西。 然而monads不仅可以编写可重用的代码,还可以抽取所有冗余的详细信息。
  作为一个例子,假设我们要过滤一个列表。  最简单的方法是使用filter函数: filter (> 3) [1..10] ,等于[4,5,6,7,8,9,10] 。 
  是一个稍微复杂一点的filter ,也是从左到右传递一个累加器 
swap (x, y) = (y, x)
(.*) = (.) . (.)
filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]
  为了得到所有的i ,使得i <= 10, sum [1..i] > 4, sum [1..i] < 25 ,我们可以写出 
filterAccum (a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
  相当于[3,4,5,6] 。 
  或者我们可以重新定义根据filterAccum从列表中删除重复元素的nub函数: 
nub' = filterAccum (a x -> (x `notElem` a, x:a)) []
  nub' [1,2,4,5,4,3,1,8,9,4]等于[1,2,4,5,3,8,9] 。  列表在这里作为累加器传递。  代码的工作原理是,因为有可能离开列表monad,所以整个计算保持纯粹( notElem实际上不使用>>=但它可以)。  然而,不可能安全地离开IO monad(即,您不能执行IO操作并返回一个纯粹的值 - 该值始终会包装在IO monad中)。  另一个例子是可变数组:在你离开ST monad后,一个可变数组存在,你不能再以恒定的时间更新数组。  所以我们需要一个来自Control.Monad模块的monadic过滤器: 
filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)
  filterM为列表中的所有元素执行filterM操作,生成单元操作返回True元素。 
数组的过滤示例:
nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs
main = print $ nub' [1,2,4,5,4,3,1,8,9,4]
  按预期打印[1,2,4,5,3,8,9] 。 
还有一个IO monad版本,它询问要返回哪些元素:
main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn
例如
return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output
  并作为最终的插图, filterAccum可以在以下方面定义filterM : 
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
  与StateT monad一样,在引擎盖下使用,仅仅是一个普通的数据类型。 
这个例子说明,monad不仅可以抽象计算上下文并编写干净的可重用代码(由于monad的可组合性,正如@Carl所解释的),而且还可以统一处理用户定义的数据类型和内置基元。
链接地址: http://www.djcxy.com/p/42915.html