我会给出一些提示来帮助您解决问题。
首先,为您定义的所有函数提供类型是一个巨大的帮助。这让编译器可以立即告诉您一个函数是否没有按照您的想法执行。当您需要将程序的不同部分连接在一起时,它还可以为您提供帮助。
其次,每个函数只解决一个问题是一个非常好的主意。您的 intotwo 函数很好地遵循了这一点,它很好地完成了拆分列表的工作。但是,您的 combine 函数看起来像是在尝试做太多事情,这会使编写变得更加困难。我会将该功能拆分为两个较小的功能。
最后,还有一个非常有用的特殊功能,叫做undefined
. 这匹配任何类型,并帮助您编写函数。诀窍是从等于未定义的整个函数开始(并在运行时编译但崩溃),并逐步改进该函数,直到它完成您想要的并且没有更多的未定义。
所以首先,我会在 intotwo 函数中添加一个类型签名。类型是[a] -> ([a], [a])
。
接下来,我将编写一个函数,从输入中读取行,直到遇到空行,然后返回读取的行列表。此函数将具有以下类型:
readLinesUntilEmpty :: IO [String]
readLinesUntilEmpty = undefined
几乎可以做到这一点的实现是:
readLinesUntilEmpty = do
nextLine <- getLine
rest <- readLinesUntilEmpty
return (nextLine : rest)
然而,这永远不会停止阅读(注意如何不检查 nextLine 是否为空)。
以下函数显示了如何执行此操作(但我将部分实现排除在外):
readLinesUntilEmpty = do
nextLine <- getLine
case nextLine of
"" -> do
undefined -- TODO fix me
_ -> do
undefined -- TODO fix me
你应该能够从那里找出其余的。
接下来,您的 intotwo 函数返回两个字符串列表,但您需要再次将它们连接在一起。例如["this", "that"]
应该变成"this\nthat"
. 这种函数的类型是[String] -> String
:
joinLines :: [String] -> String
joinLines = undefined -- TODO
这是一个比 inttotwo 函数更容易编写的函数,所以你应该没问题。
最后,您可以将这三个函数链接在一起以获得结果:
combine :: IO (String, String)
combine = do
lines <- readLinesUntilEmpty
let (oddLines, evenLines) = intotwo lines
return $ (joinLines oddLines, joinLines evenLines)