2

我有一个带有代表目录的字符串的文件。其中一些字符串中有一个波浪号 (~)。我想将用户的主目录 (~) 加入字符串的其余部分。到目前为止我所拥有的:

import Data.List (isPrefixOf)
import System.Directory (doesDirectoryExist, getHomeDirectory)
import System.FilePath (joinPath)

getFullPath s
    | "~" `isPrefixOf` s = joinPath [getHomeDirectory, tail s]
    | otherwise          = s

但我收到以下错误:

Couldn't match type `IO FilePath' with `[Char]'Expected type: FilePath Actual type: IO FilePathIn the expression: getHomeDirectoryIn the first argument of `joinPath', namely `[getHomeDirectory, tail s]'In the expression: joinPath

我不知道,也找不到,如何转换类型以使它们匹配并可以连接在一起。

4

2 回答 2

9

比@user2720372 建议的更惯用的解决方案是将非单子代码与单子代码分开。IO 动作是IOmonad 中的一元函数。

如果您只需要getFullPath在本地缓存主目录是有意义的:

fullPath homePath s
    | "~" `isPrefixOf` s = joinPath [homePath, tail s]
    | otherwise          = s

main = do
    homePath <- getHomeDirectory
    let getFullPath = fullPath homePath
    print $ getFullPath "~/foo"

如果您仍然需要完整的全局getFullPath,则可以这样实现:

getFullPath p = do
    homePath <- getHomeDirectory
    return $ fullPath homePath p

它被认为是一种很好的保持fullPathgetFullPath分离的风格。

对于这样一个简单的案例,您也不需要isPrefixOf并且tail首先:

fullPath homePath ('~' : t) = joinPath [homePath, t]
fullPath _ s = s

如果您只想要一个整体getFullPath,那么可以简化@user2720372 的变体:

getFullPath s = do
    homeDir <- getHomeDirectory
    return $ case s of
        ('~' : t) -> joinPath [homeDir, t]
        _ -> s

请注意,上面的代码只是对保留其错误行为的代码的重构:您应该~与第一个路径组件进行比较,而不是与第一个路径字符进行比较。使用splitPath来自System.FilePath

getFullPath s = do
    homeDir <- getHomeDirectory
    return $ case splitPath s of
        ("~" : t) -> joinPath $ homeDir : t
        _ -> s

此外,do-notation 仅适用于复杂情况。如果您将 do-notation 用于简单的两行代码,则几乎可以肯定它可以简化为fmap/ <$>/ >>=/ >=>/或来自and的liftM2其他函数的应用。Control.MonadControl.Applicative

这是另一个版本:

import Control.Applicative ((<$>))
import System.Directory (getHomeDirectory)
import System.FilePath (joinPath, splitPath)

getFullPath s = case splitPath s of
    "~/" : t -> joinPath . (: t) <$> getHomeDirectory 
    _ -> return s   

main = getFullPath "~/foo" >>= print

这是另一个更模块化但可读性更低的版本:

import Control.Applicative ((<$>), (<*>))
import System.Directory (getHomeDirectory)
import System.FilePath (joinPath, splitPath)

main = getFullPath "~/foo" >>= print

withPathComponents f = joinPath . f . splitPath 

replaceHome p ("~/" : t) = p : t
replaceHome _ s = s

getFullPath path = withPathComponents . replaceHome <$> getHomeDirectory <*> return path     

邀请 Haskell 大师重写它以保持模块化但提高可读性:)

于 2013-09-04T10:42:32.207 回答
3
getHomeDirectory :: IO FilePath

getHomeDirectory不是一个函数而是一个 IO 动作,所以你必须先在另一个 IO 动作中解压它。

getFullPath :: String -> IO FilePath
getFullPath s = do
    homeDir <- getHomeDirectory
    if "~" `isPrefixOf` s
        then return (joinPath [homeDir, tail s])
        else return s
于 2013-09-04T09:59:26.180 回答