(这并不能直接回答问题,但它会使您的代码更加地道,因此更易于阅读。)
您经常使用\x -> f x >>= ... 模式,这可以(并且应该)被消除:它(大部分)是不必要的噪音,掩盖了代码的含义。我不会使用您的代码,因为它是家庭作业,但请考虑这个示例(请注意,我正在使用 return,正如其他答案所建议的那样):
main = getLine >>=
\fname -> openFile fname ReadMode >>=
\handle -> hGetContents handle >>=
\str -> return (lines str) >>=
\lns -> return (length lns) >>=
\num -> print num
(它从用户那里读取一个文件名,然后打印该文件中的行数。)
最简单的优化是我们计算行数的部分(这对应于您将单词分开并获得第二个单词的部分):字符串中的行数str 只是length (lines str) (与length . lines $ str 相同),因此我们没有理由将length 的调用和lines 的调用分开。我们的代码现在是:
main = getLine >>=
\fname -> openFile fname ReadMode >>=
\handle -> hGetContents handle >>=
\str -> return (length . lines $ str) >>=
\num -> print num
现在,下一个优化是在\num -> print num。这可以写成print。 (这称为eta conversion)。 (您可以将其视为“一个接受参数并在其上调用 print 的函数,与 print 本身相同”)。现在我们有:
main = getLine >>=
\fname -> openFile fname ReadMode >>=
\handle -> hGetContents handle >>=
\str -> return (length . lines $ str) >>= print
我们可以做的下一个优化是基于monad laws。使用第一个,我们可以将return (length . lines $ str) >>= print 转换为print (length . lines $ str)(即“创建一个包含值的容器(这是由return 完成的),然后将该值传递给print,这与传递值print")。同样,我们可以删除括号,所以我们有:
main = getLine >>=
\fname -> openFile fname ReadMode >>=
\handle -> hGetContents handle >>=
\str -> print . length . lines $ str
然后看!我们可以进行 eta 转换:\str -> print . length . lines $ str 变为 print . length . lines。这会留下:
main = getLine >>=
\fname -> openFile fname ReadMode >>=
\handle -> hGetContents handle >>= print . length . lines
此时,我们可能会停下来,因为该表达式比我们原来的表达式要简单得多(如果我们愿意,我们可以继续使用>=>)。由于它非常简单,因此也更容易调试(想象一下,如果我们忘记使用lines:在原始main 中不会很清楚,在最后一个很明显。)
在您的代码中,您可以也应该这样做:您可以使用 sections(这意味着 \x -> x !! 1 与 (!! 1) 相同),以及我在上面使用的 eta 转换和 monad 法则。