我希望练习不是接触给定的代码,而是扩展它以执行您的功能。它定义了一个看起来很复杂的main 函数,并声明了一个更直接的apply 的类型,它被调用但没有定义。
import System.Environment -- contains the function getArgs
-- main gets arguments, does something to them using apply, and prints
main = getArgs >>= print . (foldr apply 0) . reverse
-- apply must have this type, but what it does must be elsewhere
apply :: String -> Integer -> Integer
如果我们专注于apply,我们会看到它接收一个字符串和一个整数,并返回一个整数。这是我们必须编写的函数,它不能决定控制流,所以我们可以在希望参数处理成功的同时得到它。
如果我们确实想弄清楚main 在做什么,我们可以做一些观察。 main 中唯一的整数是 0,因此第一次调用必须将其作为第二个参数;后面的将与返回的任何内容链接在一起,因为这就是 foldr 的运作方式。 r 代表从右边,但参数是reversed,所以这仍然处理来自左边的参数。
所以我可以继续编写一些apply 绑定来使程序编译:
apply "succ" n = succ n
apply "double" n = n + n
apply "div3" n = n `div` 3
这增加了一些可用的操作。它不处理所有可能的字符串。
$ runhaskell pmb.hs succ succ double double succ div3
3
$ runhaskell pmb.hs hello?
pmb.hs: pmb.hs:(5,1)-(7,26): Non-exhaustive patterns in function apply
练习应该是关于如何处理基于字符串参数的操作选择。有几个选项,包括上述不同的patterns、模式保护、case 和if 表达式。
检查使用的函数以了解它们如何组合在一起可能很有用。下面来看看ghci中用到的几个函数:
Prelude> import System.Environment
Prelude System.Environment> :t getArgs
getArgs :: IO [String]
Prelude System.Environment> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Prelude System.Environment> :t print
print :: Show a => a -> IO ()
Prelude System.Environment> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Prelude System.Environment> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude System.Environment> :t reverse
reverse :: [a] -> [a]
这说明所有的字符串都是从getArgs出来的,它和print在IO monad中运行,必须是>>=中的m,而.从右边传递结果函数转换为左函数的参数。但是,类型签名本身并不能告诉我们foldr 处理事情的顺序,或者reverse 做了什么(尽管它不能创建新值,只能重新排序,包括重复)。
作为最后一个练习,我将重写main 函数,使其不会多次切换方向:
main = print . foldl (flip apply) 0 =<< getArgs
这在数据流意义上从右到左读取并从左到右处理参数,因为foldl 执行左关联折叠。 flip 只是为了匹配 apply 的参数顺序。