我正在尝试学习Haskell。我想创建一个小程序,该程序从当前目录中获取文件列表,逐个读取它们并打印其第一行。我尝试过:
import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
getAbsoluteDirContents :: String -> IO [FilePath]
getAbsoluteDirContents dir = do
contents <- getDirectoryContents dir
return $ map (dir </>) contents
readALine x = do
print $ "Filename: " ++ x
f <- hGetLine x
print $ "First line: " ++ f
main = do
files <- getAbsoluteDirContents "."
print files
map (readALine f) files
但是,此代码给出了以下详细错误:
$ ghc -o firstline.hsx firstline.hs
[1 of 1] Compiling Main ( firstline.hs, firstline.o )
firstline.hs:13:9: error:
• Variable not in scope: hGetLine :: [Char] -> IO [Char]
• Perhaps you meant ‘getLine’ (imported from Prelude)
firstline.hs:19:3: error:
• Couldn't match type ‘[]’ with ‘IO’
Expected type: IO b
Actual type: [b]
• In a stmt of a 'do' block: map (readALine f) files
In the expression:
do { files <- getAbsoluteDirContents ".";
print files;
map (readALine f) files }
In an equation for ‘main’:
main
= do { files <- getAbsoluteDirContents ".";
print files;
map (readALine f) files }
firstline.hs:19:8: error:
• Couldn't match expected type ‘FilePath -> b’
with actual type ‘IO ()’
• Possible cause: ‘readALine’ is applied to too many arguments
In the first argument of ‘map’, namely ‘(readALine f)’
In a stmt of a 'do' block: map (readALine f) files
In the expression:
do { files <- getAbsoluteDirContents ".";
print files;
map (readALine f) files }
• Relevant bindings include
main :: IO b (bound at firstline.hs:16:1)
firstline.hs:19:18: error: Variable not in scope: f :: [Char]
我认为主要问题是hGetLine
功能不正确。我看到了此功能here。
map (readALine f) files
表达式也有问题。
问题出在哪里,如何解决?感谢您的帮助。
答案 0 :(得分:2)
您忘记了导入文件中的功能,例如:
import Data.Text.IO(hGetLine)
但是这里仍然存在很多错误,正弦hGetLine
期望Handle
不是文件路径。您需要先使用openFile :: FilePath -> IOMode -> IO Handle
打开文件,并且在读取行之后需要使用hClose :: Handle -> IO ()
关闭句柄。
在您的main
中写:
map (readALine f) files
但此处未定义f
。如果要应用readALine
,请编写readALine
。由于readALine
的类型为String -> IO ()
,因此您应该使用mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
。
最后,由于hGetLine :: Handler -> IO Text
返回IO Text
而不是IO String
,因此您无法将"first line: " :: String
连接到它,我们可以使用OverloadedStrings
编译指示,这样"first line: "
在这里将被解释为Text
,并使用`(<>) :: Semigroup s => s -> s -> s来进行串联。
通常,最好也指定顶级功能的签名,尽管并非严格要求,但它会显示一些有关该功能期望作为输入以及其功能的信息。
{-# LANGUAGE OverloadedStrings #-}
import Data.Semigroup((<>))
import Data.Text.IO(hGetLine)
import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
import System.IO(IOMode(ReadMode), openFile, hClose)
getAbsoluteDirContents :: String -> IO [FilePath]
getAbsoluteDirContents dir = do
contents <- getDirectoryContents dir
return $ map (dir </>) contents
readALine :: FilePath -> IO ()
readALine x = do
print $ "Filename: " ++ x
h <- openFile x ReadMode
f <- hGetLine h
hClose h
print $ "First line: " <> f
main :: IO ()
main = do
files <- getAbsoluteDirContents "."
print files
mapM_ readALine files
话虽如此,该程序在语义上仍然无法正常运行,因为您的程序将旨在打开目录(也像.
和..
这样)。因此,您还需要实现适当的过滤机制以防止打开目录。您可以使用isFile :: FilePath -> IO Bool
,我将其保留为练习。