我写了这段代码:
toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode
outputFile <- openFile "couples.txt" WriteMode
readAndChange inputFile outputFile
readAndChange i o = do iseof <- hIsEOF i
if iseof then (return o)
else do line <- hGetLine i
hPutStrLn o (show (extractNameAndId line))
readAndChange i o
我想知道是否可以使用一个函数重写此代码,使用类似于此模式的东西:
function x = do ...
label
.....
if ... then label else exit
答案 0 :(得分:13)
你通过不必要的强制性编程来改变生活。您正在使用漂亮的Haskell语言进行编程,而您正在寻找goto
构造!
为什么不只是import Control.Applicative (<$>)
并写
readAndChange' = writeFile "couples.txt" =<<
unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv"
(是的,这几乎是一个单行。它是干净的,功能性的,并且通过读写线的机制整洁。尽可能多的处理是在纯代码中完成的,只有输入和输出是IO-基础的。)
<强>解释强>
此处unlines.map (show.extractNameAndId).lines
处理您的输入,方法是将其整理成行,然后使用extractNameAndId
将show
然后map
应用于每个输入,然后使用{{1}将它们重新连接在一起}}
unlines
将读取文件并应用处理功能。 unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv"
是<$>
令人愉快的语法。
fmap
与writeFile "couples.txt" =<< getanswer
相同 - 按上述方式获取答案,然后将其写入文件。
尝试编写getanswer >>= writeFile "couples.txt"
然后在ghci中为了好玩而做这些
greet xs = "hello " ++ xs
最后一个是我们在greet "Jane" -- apply your pure function purely
greet $ "Jane" -- apply it purely again
greet <$> ["Jane","Craig","Brian"] -- apply your function on something that produces three names
greet <$> Just "Jane" -- apply your function on something that might have a name
greet <$> Nothing -- apply your function on something that might have a name
greet <$> getLine -- apply your function to whatever you type in
greet <$> readFile "deletedId.csv" -- apply your function to your file
中使用<$>
的方式。如果有很多数据
deletedId.csv你会错过你好,但你当然可以做到
readAndChange
查看前4行。
所以greet <$> readFile "deletedId.csv" >>= writeFile "hi.txt"
take 4.lines <$> readFile "hi.txt"
允许你在你给它的参数上使用你的函数。 $
所以如果你写greet :: String -> String
,greet $ person
必须是person
类型,而如果你写String
,greet <$> someone
可以是产生someone
的任何内容 - 字符串列表,String
,IO String
。从技术上讲,Maybe String
,但您应该首先阅读类型类和Applicative Functors。了解一下Haskell for Great Good是一个很好的资源。
为了获得更多乐趣,如果你有一个带有多个参数的函数,你仍然可以使用可爱的Applicative样式。
someone :: Applicative f => f String
尝试
insult :: String -> String -> String
insult a b = a ++ ", you're almost as ugly as " ++ b
这里你在函数之后使用insult "Fred" "Barney"
insult "Fred" $ "Barney"
insult <$> ["Fred","Barney"] <*> ["Wilma","Betty"]
insult <$> Just "Fred" <*> Nothing
insult <$> Just "Fred" <*> Just "Wilma"
insult <$> readFile "someone.txt" <*> readFile "someoneElse.txt"
,在它需要的参数之间使用<$>
。它的工作原理一开始有点令人兴奋,但它是编写有效计算的最具功能性的风格。
接下来阅读有关Applicative Functors的内容。他们很棒。
http://learnyouahaskell.com/functors-applicative-functors-and-monoids
http://en.wikibooks.org/wiki/Haskell/Applicative_Functors
答案 1 :(得分:3)
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Either
readAndChange i o = do
result <- fmap (either id id) $ runEitherT $ forever $ do
iseof <- lift $ hIsEof i
when iseof $ left o -- Break and return 'o'
line <- lift $ hGetLine i
lift $ hPutStrLn o $ show $ extractNameAndId line
-- 'result' now contains the value of 'o' you ended on
doSomeWithingWith result
要了解此技术的工作原理,请阅读this。
答案 2 :(得分:3)
您可以使用let
模式进行递归,但这与分别定义递归函数类似:
main = do
let x = 10
let loop = do
print 1
when (x<20) loop
loop
您也可以使用fix
中的Control.Monad.Fix
来实现类似行为
main = do
let x = 10
fix $ \loop -> do
print 1
when (x<20) loop
你所说的是goto label
模式。我不知道你可以实现这种行为,但上面使用fix
或let
可以很容易地帮助你实现递归。
[编辑] 还有一些模式可以实现类似的结果,比如使用
中的Cont
monad
getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))
main = (`runContT` return) $ do
(x, loopBack) <- getCC' 0
lift (print x)
when (x < 10) (loopBack (x + 1))
lift (putStrLn "finish")
将从1 to 10
打印数字。有关更好的说明,请参阅goto using continuation。
还有Goto monad和变压器。我没用过它。您可能会发现它适合您的需要。
答案 3 :(得分:2)
您应该做的第一件事是阅读Control.Monad
模块的文档,这对于编写Haskell代码是绝对必要的。当你在这里时,从Hackage安装Control.Monad.Loops
包,并阅读相关的文档;你可能对那里的whileM_
函数特别感兴趣:
import Data.Functor ((<$>)
import Control.Monad.Loops (whileM_)
readAndChange i o =
whileM_ notEOF $ do line <- hGetLine i
hPutStrLn o (show (extractNameAndId line))
where notEOF = not <$> (hIsEOF i)
有问题的图书馆像这样实施whileM_
,这是你正在寻找的模式:
-- |Execute an action repeatedly as long as the given boolean expression
-- returns True. The condition is evaluated before the loop body.
-- Discards results.
whileM_ :: (Monad m) => m Bool -> m a -> m ()
whileM_ p f = do
x <- p
if x
then do
f
whileM_ p f
else return ()
但是,我必须同意你以过分强制的方式写这篇文章。试着这样想:你的程序基本上是将输入字符串转换为输出字符串。这立即表明程序逻辑的核心应该具有以下类型:
transformInput :: String -> String
transformInput = ...
您的转型是逐行进行的。这意味着您可以通过这种方式优化草图(lines
函数将字符串拆分为行; unlines
重新加入列表):
transformInput :: String -> String
transformInput input = unlines (map transformLine (lines input))
transformLine :: String -> String
transformLine line = show (extractNameAndId line)
现在你已经获得了transformInput
函数中逻辑的核心,所以你只需要以某种方式将它连接到输入和输出句柄。如果你正在处理stdin和stdout,你可以使用interact
函数来做到这一点。但我们实际上可以窃取它的实现并修改它:
hInteract :: Handle -> Handle -> (String -> String) -> IO ()
hInteract i o f = do s <- hGetContents i
hPutStr o (f s)
现在,瞧:
toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode
outputFile <- openFile "couples.txt" WriteMode
hInteract inputFile outputFile transformInput
警告:所有代码都未经测试。
最后一件事,为了充分披露:这里的诀窍是hGetContents
执行懒惰I / O :它基本上允许您将句柄的全部内容视为a String
,因此将transformInput
函数应用于句柄的内容。但这一切都是懒洋洋地完成的,所以它实际上不必立即读取整个文件。
这是最简单的方法,你应该学习它,但它有一个很大的弱点,那就是你可以在关闭句柄时失去一些控制权。但是,对于快速和脏的程序,这是可以的。
答案 4 :(得分:0)
与命令式编程语言不同,并且与其他函数式编程语言不同,Haskell不包含用于编写for循环或while循环的语法结构,这就是你在这里要求的。
这个想法是递归过程和迭代过程可以通过递归函数统一捕获。只是迭代过程被捕获为特定类型的递归函数:这些函数是尾递归。诸如do-blocks中出现的命令式代码也不例外。你可能会发现这个缺乏明确的循环结构很烦人,因为你必须为每个循环定义一个新函数,因此在某种意义上必须命名循环。然而,由于三个原因,这是为Haskell方法的一致性和简单性付出的微不足道的代价:
您不必定义代表顶层循环的函数。您可以在本地定义它。
在Haskell中,许多人通常总是对这些类型的循环使用相同的名称。这里的热门选择是go
和aux
。因此,您的代码可以按如下方式重写:
toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode
outputFile <- openFile "couples.txt" WriteMode
let go = do
iseof <- hIsEOF inputFile
if iseof then (return outputFile)
else do line <- hGetLine inputFile
hPutStrLn outputFile (show (extractNameAndId line))
go
go
最后,缺少循环结构非常无关紧要,因为我们通常根本不需要编写循环。在您的情况下,此主题中的其他答案已经显示了许多方法。