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 アクションはモナドのモナド関数IOです。

ローカルでのみ必要な場合は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記法は複雑な場合のみです。単純な 2 ライナーに do 表記を使用すると、ほぼ確実にfmap/ <$>/ >>=/ >=>/またはおよび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 に答える