我必须在Haskell中实现一个小程序,该程序根据控制台行中的内容递增/递减结果。例如,如果我们在控制台中有 -a,则结果必须为 0,如果 -b,则结果必须递增 6,依此类推。我必须通过模式匹配来做到这一点。
直到现在我还没有使用过Haskell,我发现它很难理解。我有这个开始:
import System.Environment
main = getArgs >>= print . (foldr apply 0) . reverse
apply :: String -> Integer -> Integer
我不明白主要是什么。它做了什么,从头到尾反之亦然,它做了什么?正如我在互联网上读到的那样,getArgs 函数为我提供了控制台行的值。但是我该如何使用它们呢?在 Haskell 中是否有像 for/while 这样的等效函数?
另外,如果你有一些例子或者可以帮助我,我将非常感激。
谢谢!
这不是适合初学者的代码。那里采用了几个快捷方式来保持代码非常紧凑(并且是无点形式(。代码
main = getArgs >>= print . (foldr apply 0) . reverse
可以按如下方式扩展
main = do
args <- getArgs
let reversedArgs = reverse args
result = foldr apply 0 reversedArgs
print result
其结果如下。如果命令行参数是,比如说,args = ["A","B","C"]
,那么我们得到reversedArgs = ["C","B","A"]
,最后
result = apply "C" (apply "B" (apply "A" 0))
因为foldr
以这种方式应用函数apply
。
老实说,我不确定为什么代码使用reverse
和foldr
来完成您的任务。我会考虑foldl
(或者,为了提高性能,foldl'
(代替。
我希望练习不是接触给定的代码,而是扩展它以执行您的功能。它定义了一个看起来很复杂的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
代表从右边,但参数是reverse
d,所以这仍然处理来自左边的论点。
所以我可以继续编写一些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
该练习应该是关于如何处理基于字符串参数的操作选择。有几个选项,包括上述不同的模式、模式防护、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
的参数顺序.
正如评论中所建议的,hoogle是一个很棒的工具。 要了解您从getArgs
中得到的确切内容,您可以在hoogle上搜索它:
https://hackage.haskell.org/package/base-4.11.1.0/docs/System-Environment.html#v:getArgs 如您所见,它属于IO [String]
型 . 由于我不知道您对 IO 抽象有多熟悉,我们只想说>>=
的正确部分将它们作为参数。
像./a.out -a -b --asdf Hi
这样的调用的参数将是字符串列表:
["-a", "-b", "--asdf", "Hi"]
.
然后,main 中的 fold + reverse 将产生一些魔术,并且您的apply
函数将使用列表中的每个字符串和之前的返回值(第一次调用0
(调用。
在 Haskell 中,String
与[Char]
相同,但带有一点编译器糖,因此您可以像在apply
定义中的常规列表一样匹配字符串。