为什么 Data.Bifunctor 中的 `first` 不转换这个值

Why doesn't `first` from Data.Bifunctor transform this value

在下面:

import Data.Bifunctor
import qualified Data.ByteString.Lazy.UTF8 as BLU

safeReadFile :: FilePath -> ExceptT Text IO Text
safeReadFile p = (lift $ doesFileExist p) >>= bool (throwError "File does not exist") (lift $ pack <$> readFile p)

safeDecodeJSONFile :: FromJSON a => Text -> FilePath -> ExceptT Text IO a
safeDecodeJSONFile t f = do
  contents <- safeReadFile f
  tryRight $ first (\x -> pack (x ++ (unpack t))) (eitherDecode (BLU.fromString (unpack contents)))

当我 运行 runExceptT $ safeDecodeJSONFile "something" "nonExistantFile.json" 我希望得到 Left "Does not exist something" 但我只是得到 Left "Does not exist" - 我知道我传递给 first 的函数正在执行,因为没有 pack GHC 抱怨 (eitherDecode (BLU.fromString (unpack contents))) 的类型是 ExceptT String IO a 而不是 ExceptT Text IO a - 那么为什么 ++ 的连接不是也会发生?

你写了

safeDecodeJSONFile t f = do
  contents <- safeReadFile f
  tryRight $ ...

ExceptTMonad 实例在遇到 Left 时立即放弃,并准确返回。所以 tryRight ... 永远不会发生。您需要明确处理 Left 案例,也许使用 catchError.

虽然我们正在这样做,但仍然存在问题。你写

safeReadFile :: FilePath -> ExceptT Text IO Text
safeReadFile p = (lift $ doesFileExist p) >>= bool (throwError "File does not exist") (lift $ pack <$> readFile p)

不幸的是,这并不可靠。首先,文件不存在只是 一个 读取失败的原因——可能存在权限错误、网络文件系统的网络问题、文件不是常规文件时的设备错误等。其次,在您检查文件是否存在和您尝试读取它之间,其他人可能 删除 文件。尝试处理文件时通常的建议是不要先检查。只需阅读文件并使用 catchControl.Exception 中的类似内容或围绕它们的包装器

捕获任何异常