concatMap
是这里最直观的东西。这种在数据结构上映射一个本身返回数据结构类型(在本例中为列表)的函数和将结果组合回单个“紧密”列表之间的组合在 Haskell中确实很常见,并且确实不仅适用于列表。
我想解释一下为什么你的第一次尝试会编译,以及它实际上做了什么——因为它与你可能想的完全不同!
apply (x:xs) = if (x == 'F')
该行仍然非常清楚:您只需从字符串中取出第一个字符并将其与'F'
. 在位“行人”手动将字符串分开,但很好。好吧,你给这个函数起的名字不是特别好,但我会在这里坚持下去。
then do show "Hello"
现在这很有趣。您可能会认为do
开始一个点列表,“先做这个,然后做那个”......就像在简单的Hello, World -ish 示例程序中一样。但永远记住:在 Haskell 中,通常没有计算东西的顺序这样的东西。这只发生在IO
上下文中。但是IO
您的代码中没有!?!
不确定你是否听说过IO
实际上是什么,不管怎样,你去吧:它是一个Monad
. 那些“你只在故事书中读过的神话般的 Haskell 构造”......
事实上,尽管这可能会导致这里有点远,但这个问题涵盖了所有关于Monad
s 的知识!那个怎么样?
这是另一种(正确的!)方式来定义您的功能。
apply' str = do
x <- str
if (x == 'F')
then "FLF"
else return x
所以我使用了这种奇怪do
的语法,它不在 中IO
,它看起来与你在 中写的完全不同IO
,但它确实有效。如何?
x <- str
在do
符号中,variable <- action
总是意味着“从这个单子事物中取出一个值,然后调用它x
”。你可能已经看到了类似的东西
response <- getLine
这意味着“将用户输入从现实世界中取出(从IO
单子中取出!)并调用它response
”。在x <- str
中,它是我们拥有的字符串,而不是IO
动作。所以我们从字符串中取出一个字符——既简单又好用!
但实际上,这并不完全正确。“取一个字符”是你用 做的apply (x:xs) = ...
,它只取第一个。相反,x <- str
实际上从字符串中取出所有可能的字符,一个接一个。如果您习惯于过程语言,这可能看起来与 非常不一致response <- getLine
,但实际上并非如此:getLine
它还包含用户可能给出的所有可能输入,并且程序必须按照此操作。
if (x == 'F')
这里没有什么意外,但是
then "FLF"
哇!就这样?我们先看下一行
else return x
好的,这看起来很熟悉,但实际上并非如此。在其他语言中,这意味着“我们完成了我们的功能,x
就是结果”。但这显然不是这里发生的事情,因为x
is和isChar
的“返回类型” 。在 Haskell 中,实际上与从函数返回值几乎没有关系,而是意味着“将该值放入我们正在处理的一元上下文中”。如果 monad 是,那将是完全相同的:将此值返回给现实世界的上下文(这并不意味着打印该值或其他东西,只是将其交给)。但是在这里,我们的上下文是一个字符串,或者更确切地说是一个列表(字符,所以它是a )。apply'
String
return
IO
String
对,所以如果x
不是'F'
,我们将其放回字符串中。这听起来很合理,但是呢then "FLF"
?请注意,我也可以这样写:
if (x == 'F')
then do
x' <- "FLF"
return x'
else return x
这意味着,我将所有字符取出"FLW"
并将它们返回到整体结果中。但是没有必要只考虑最终结果,我们也可以只隔离这部分do { x' <- "FLF"; return x' }
——而且,很明显,它的价值就是字符串"FLF"
本身!
所以我希望你现在已经掌握了为什么apply'
有效。回到你的版本,虽然它实际上没有多大意义......
then do
show "Hello"
apply xs
在这里,我们有一行不在do
块的末尾,但里面没有 a <-
。你通常会IO
在类似的地方看到这个
main = do
putStrLn "How ya doin'?"
response <- getLine
...
请记住,“仅输出”操作IO()
在 Haskell 中具有类型,这意味着它们不会直接返回任何有意义的值,而只是返回微不足道的值()
。所以你并不真正关心这个,但你仍然可以评估它:
main = do
trivial <- putStrLn "Hello, let's see what this IO action returns:"
print trivial
编译和输出
你好,让我们看看这个 IO 动作返回什么:(
)
如果我们必须一直进行这种评估,那将是愚蠢()
的,因此 Haskell 允许将其() <-
排除在外。真的就是这样!
所以像块show "Hello"
中间这样的一行do
基本上意味着“取出一个字符show "Hello"
(它只是一个带有 value 的字符串"\"Hello\""
),但不要对这个字符做任何其他事情/只是把它扔掉”。
您定义的其余部分只是对 的其他递归调用apply
,但是因为它们都没有比丢弃字符更有趣的事情,所以您最终会得到apply [] = []
,所以这就是最终结果:一个空字符串。