比@user2720372 建议的更惯用的解决方案是将非单子代码与单子代码分开。IO 动作是IO
monad 中的一元函数。
如果您只需要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
它被认为是一种很好的保持fullPath
和getFullPath
分离的风格。
对于这样一个简单的案例,您也不需要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.Monad
Control.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 大师重写它以保持模块化但提高可读性:)