我希望让我的 Haskell 程序从外部文件读取设置,以避免重新编译以进行微小的更改。熟悉 YAML,我认为这将是一个不错的选择。现在我必须把这两部分放在一起。到目前为止,谷歌并不是很有帮助。
非常感谢处理从文件中读取和解构 YAML 的小示例代码。
如果我对可用的软件包感兴趣,我会去 hackage,查看完整的软件包列表,然后在页面中搜索关键字。这样做会带来这些选择(以及其他一些不太引人注目的选择):
和一个名为 yaml-light 的 HsSyck 包装器:http: //hackage.haskell.org/package/yaml-light
yaml 和 HsSyck 看起来都是最近更新的,并且似乎被其他广泛使用的软件包使用。您可以通过检查反向部门来看到这一点:
两者中,yaml 有更多的 deps,但那是因为它是 yesod 生态系统的一部分。一个依赖于 HsSyck 的库是 yst,我碰巧知道它是积极维护的,所以这向我表明 HsSyck 也很好。
做出选择的下一步是浏览这两个库的文档,看看哪个对我的目的更有吸引力。
在这两者中,看起来 HsSyck 暴露了更多的结构,但其他的不多,而 yaml 则通过 aeson 提供的 json 编码。这向我表明,前者可能更强大,而后者更方便。
一个简单的例子:
首先你需要一个test.yml
文件:
db: /db.sql
limit: 100
在 Haskell 中读取 YAML
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Data.Yaml
data Config = Config { db :: String
, limit :: Int
} deriving (Show, Generic)
instance FromJSON Config
main :: IO ()
main = do
file <- decodeFile "test.yml" :: IO (Maybe Config)
putStrLn (maybe "Error" show file)
您可以以静态分析友好的方式yamlparse-applicative
描述您的 YAML 解析器,因此您可以免费从解析器获取 YAML 格式的描述。我将为此使用 Matthias Braun 的示例格式:
{-# LANGUAGE ApplicativeDo, RecordWildCards, OverloadedStrings #-}
import Data.Yaml
import Data.Aeson.Types (parse)
import YamlParse.Applicative
import Data.Map (Map)
import qualified Data.Text.IO as T
data MyType = MyType
{ stringsToStrings :: Map String String
, mapOfLists :: Map String [String]
} deriving Show
parseMyType :: YamlParser MyType
parseMyType = unnamedObjectParser $ do
stringsToStrings <- requiredField' "strings_to_strings"
mapOfLists <- requiredField' "map_of_lists"
pure MyType{..}
main :: IO ()
main = do
T.putStrLn $ prettyParserDoc parseMyType
yaml <- decodeFileThrow "config/example.yaml"
print $ parse (implementParser parseMyType) yaml
请注意,main
甚至可以在看到实例之前打印出架构:
strings_to_strings: # required
<key>: <string>
map_of_lists: # required
<key>: - <string>
Success
(MyType
{ stringsToStrings = fromList
[ ("key_one","val_one")
, ("key_two","val_two")
]
, mapOfLists = fromList
[ ("key_one",["val_one","val_two","val_three"])
, ("key_two",["val_four","val_five"])
]
})
以下是使用该yaml
库从 YAML 文件中解析特定对象的方法。
让我们解析这个文件的部分内容config/example.yaml
:
# A map from strings to strings
strings_to_strings:
key_one: val_one
key_two: val_two
# A map from strings to list of strings
map_of_lists:
key_one:
- val_one
- val_two
- val_three
key_two:
- val_four
- val_five
# We won't parse this
not_for: us
该模块单独解析strings_to_strings
并将map_of_lists
它们放入自定义记录中MyType
:
{-# Language OverloadedStrings, LambdaCase #-}
module YamlTests where
import Data.Yaml ( decodeFileEither
, (.:)
, parseEither
, prettyPrintParseException
)
import Data.Map ( Map )
import Control.Applicative ( (<$>) )
import System.FilePath ( FilePath
, (</>)
)
data MyType = MyType {stringsToStrings :: Map String String,
mapOfLists :: Map String [String]} deriving Show
type ErrorMsg = String
readMyType :: FilePath -> IO (Either ErrorMsg MyType)
readMyType file =
(\case
(Right yamlObj) -> do
stringsToStrings <- parseEither (.: "strings_to_strings") yamlObj
mapOfLists <- parseEither (.: "map_of_lists") yamlObj
return $ MyType stringsToStrings mapOfLists
(Left exception) -> Left $ prettyPrintParseException exception
)
<$> decodeFileEither file
yamlTest = do
parsedValue <- readMyType $ "config" </> "example.yaml"
print parsedValue
运行yamlTest
查看解析结果。