我为一个非常简单的练习题写了一个解决方案:
假设每个方块上的数字翻倍,计算棋盘上的小麦粒数。编写代码来显示给定方块上有多少谷物,以及棋盘上的谷物总数。
如果输入小于 1 或大于 64,则函数必须返回 aMaybe Integer并返回Nothing。
square :: Integer -> Maybe Integer
square n = if (n < 1 || n > 64) then Nothing else Just (2^(pred n))
total :: Integer
total = sum (fmap sum (map square [1..64]))
Run Code Online (Sandbox Code Playgroud)
我尝试应用fmap sum到GHCI中map square(list of Maybe Integer) 的一些测试输出,并惊讶地发现它返回整数列表 (sans Just) 而不是它们的总和。所以在上面的解决方案中,我sum第二次申请实际得到总和。
我想从概念上理解为什么会这样:换句话说,为什么sum在这种情况下表现得像一个从 Maybe Ints 到 Ints 的转换器,而不是添加东西?
我已经解决了一些依赖辅助函数的类似练习,以避免对Maybe值进行计算的复杂性,也许在这种情况下,我应该只计算 的值total而不使用square,即:
total = sum [2^n | n <- [0..63]]
Run Code Online (Sandbox Code Playgroud)
然而,由于我已经编写了一个有用的函数,我的第一直觉是重用它,这导致了一些不可预见的行为。
让我们看看类型sum:
sum :: (Foldable t, Num a) => t a -> a
Run Code Online (Sandbox Code Playgroud)
通常,对于初学者来说,这是通过假设 简化的t ~ [],因此我们改为使用sumas
sum :: Num a => [a] -> a
Run Code Online (Sandbox Code Playgroud)
如果我们尝试sum在你的例子中使用这种类型,我们会得到一个类型错误,因为你有一个可能的数字列表,而不是一个数字列表。取而代之的是,您编写fmap sum [Just 1]、 专业sum和fmap到:
sum :: Maybe Integer -> Integer
fmap :: (Maybe Integer -> Integer) -> [Maybe Integer] -> [Integer]
Run Code Online (Sandbox Code Playgroud)
所以问题不是真正的“为什么不sum添加东西”,而是“sum在给定单个 Maybe Integer 时如何有一个有意义的定义?”
回答这个问题的一种方法是,如果你不熟悉如何解释sum为正在工作Foldable或如何Maybe折叠,那就是尝试自己实现它。实际上只有一种合理的实现:
sum :: Maybe Integer -> Integer
sum Nothing = 0
sum (Just x) = x
Run Code Online (Sandbox Code Playgroud)
对?有人问你“这里的数字总数是多少”,然后给了你零或一个数字。很容易加起来。这正是sumMaybe 的工作方式,除了它通过 Foldable 而不是专门用于 Maybe 。
在这之后,当然很容易:你已经把你的[Maybe Integer]变成了一个[Integer],当然还有summing ,这让你得到了非Nothing条目的总和。
让我们看一个例子。
map square [0..2] = [Nothing, Just 1, Just 2]
fmap sum (map square [0..2]) = [sum Nothing, sum (Just 1), sum (Just 2)]
Run Code Online (Sandbox Code Playgroud)
由于Maybe是一个Foldable容器,因此计算其元素的总和是有意义的:
sum Nothing = 0
sum (Just a) = a
Run Code Online (Sandbox Code Playgroud)
所以
fmap sum (map square [0..2]) = [0, 1, 2]
Run Code Online (Sandbox Code Playgroud)
现在我不知道你实际上希望用Maybes做什么,但这就是为什么你得到你所得到的。