如何在 IO 中使用 Aeson 的解析器
How to use Parsers from Aeson with IO
我有很多字段的数据类型,如果不是由 JSON 配置文件手动指定,应该随机设置。我正在使用 Aeson 来解析配置文件。执行此操作的最佳方法是什么?
目前,我正在将值设置为某个不可能的值,然后检查该值以进行编辑。
data Example = Example { a :: Int, b :: Int }
default = Example 1 2
instance FromJSON Example where
parseJSON = withObject "Example" $ \v -> Example
<$> (v .: "a" <|> return (a default))
<*> (v .: "b" <|> return (b default))
initExample :: Range -> Example -> IO Example
initExample range (Example x y) = do
a' <- if x == (a default) then randomIO range else return x
b' <- if y == (b default) then randomIO range else return y
return $ Example a' b'
我想要的是:
parseJSON = withObject "Example" $ \v -> Example
<$> (v .: "a" <|> return (randomRIO (1,10))
是否可以在 IO Monad 中定义解析器或沿着一些随机生成器线程,最好使用 Aeson?
好吧,我不知道这是否是个好主意,并且兼顾额外的 IO
层对于更大的开发来说肯定会令人沮丧,但是以下类型检查:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import Data.Aeson
import System.Random
data Example = Example { a :: Int, b :: Int } deriving (Eq, Ord, Read, Show)
instance FromJSON (IO Example) where
parseJSON = withObject "Example" $ \v -> liftA2 Example
<$> ((pure <$> (v .: "a")) <|> pure (randomRIO (3, 4)))
<*> ((pure <$> (v .: "b")) <|> pure (randomRIO (5, 6)))
在最后两行中,第一行 pure
是 Int -> IO Int
,第二行是 IO Int -> Parser (IO Int)
。在 ghci:
> sequence (decode "{}") :: IO (Maybe Example)
Just (Example {a = 4, b = 6})
由于 ParseJSON monad 不是转换器或基于 IO,因此我不知道有什么好的策略可以到达您想去的地方。您可以更轻松地做的是解码为一种类型,然后转换为第二种类型,就像在先前的问题“”中所做的那样。
由于大型结构重现起来很麻烦,您可以将结构参数化并使用 IO Int
或 Int
对其进行实例化。例如,假设您想要来自网络的字段 a
但 b
来自 IO monad:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
import Data.Aeson
import System.Random
import Data.ByteString.Lazy (ByteString)
data Example' a =
Example { a :: Int
, b :: a
} deriving (Show,Functor,Foldable,Traversable)
type Partial = Example' (IO Int)
type Example = Example' Int
instance FromJSON Partial where
parseJSON (Object o) =
Example <$> o .: "a"
<*> pure (randomRIO (1,10))
loadExample :: Partial -> IO Example
loadExample = mapM id
parseExample :: ByteString -> IO (Maybe Example)
parseExample = maybe (pure Nothing) (fmap Just . loadExample) . decode
注意 loadExample
如何使用我们的 traverse
实例来执行结构内的 IO 操作。这是一个使用示例:
Main> parseExample "{ \"a\" : 1111 }"
Just (Example {a = 1111, b = 5})
高级
如果您有不止一种类型的字段需要 IO 操作,您可以
为所有这些创建一种数据类型。您可以将 b
设为 IO Int
,而不是 IO MyComplexRecord
。这是简单的解决方案。
更复杂有趣的解决方案是使用更高种类的类型参数。
对于选项 2,请考虑:
data Example' f = Example { a :: Int
, b :: f Int
, c :: f String }
然后您可以使用 Proxy
和 Control.Monad.Identity
来代替之前使用的 IO Int
和 Int
等值。您需要编写自己的遍历,因为您无法为此 class 推导 Traverse
(这就是我们上面使用的 mapM
的原因)。我们可以使用一些扩展(其中有 RankNTypes)用 kind (* -> *) -> *
进行遍历 class,但除非经常这样做,并且我们得到某种派生支持或 TH,我不认为值得。
这是另一个解决方案,它需要更多的体力劳动,但方法非常简单 - 生成一个随机数 IO Example
使用它来生成一个随机数 "parser"。解码成 JSON 是用通常的 decode
函数完成的。
{-# LANGUAGE OverloadedStrings #-}
module Test where
import Data.Aeson
import Data.Aeson.Types
import System.Random
data Example = Example {_a :: Int, _b :: Int} deriving (Show, Ord, Eq)
getExample :: IO (Value -> Maybe Example)
getExample = do
ex <- randomRIO (Example 1 1, Example 10 100)
let ex' = withObject "Example" $ \o ->
do a <- o .:? "a" .!= _a ex
b <- o .:? "b" .!= _b ex
return $ Example a b
return (parseMaybe ex')
instance Random Example where
randomRIO (low,hi) = Example <$> randomRIO (_a low,_a hi)
<*> randomRIO (_b low,_b hi)
...
main :: IO ()
main = do
getExample' <- getExample
let example = getExample' =<< decode "{\"a\": 20}"
print example
我不确定,但我相信这是@DanielWagner 解决方案的更详细的实现。
我有很多字段的数据类型,如果不是由 JSON 配置文件手动指定,应该随机设置。我正在使用 Aeson 来解析配置文件。执行此操作的最佳方法是什么?
目前,我正在将值设置为某个不可能的值,然后检查该值以进行编辑。
data Example = Example { a :: Int, b :: Int }
default = Example 1 2
instance FromJSON Example where
parseJSON = withObject "Example" $ \v -> Example
<$> (v .: "a" <|> return (a default))
<*> (v .: "b" <|> return (b default))
initExample :: Range -> Example -> IO Example
initExample range (Example x y) = do
a' <- if x == (a default) then randomIO range else return x
b' <- if y == (b default) then randomIO range else return y
return $ Example a' b'
我想要的是:
parseJSON = withObject "Example" $ \v -> Example
<$> (v .: "a" <|> return (randomRIO (1,10))
是否可以在 IO Monad 中定义解析器或沿着一些随机生成器线程,最好使用 Aeson?
好吧,我不知道这是否是个好主意,并且兼顾额外的 IO
层对于更大的开发来说肯定会令人沮丧,但是以下类型检查:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import Data.Aeson
import System.Random
data Example = Example { a :: Int, b :: Int } deriving (Eq, Ord, Read, Show)
instance FromJSON (IO Example) where
parseJSON = withObject "Example" $ \v -> liftA2 Example
<$> ((pure <$> (v .: "a")) <|> pure (randomRIO (3, 4)))
<*> ((pure <$> (v .: "b")) <|> pure (randomRIO (5, 6)))
在最后两行中,第一行 pure
是 Int -> IO Int
,第二行是 IO Int -> Parser (IO Int)
。在 ghci:
> sequence (decode "{}") :: IO (Maybe Example)
Just (Example {a = 4, b = 6})
由于 ParseJSON monad 不是转换器或基于 IO,因此我不知道有什么好的策略可以到达您想去的地方。您可以更轻松地做的是解码为一种类型,然后转换为第二种类型,就像在先前的问题“
由于大型结构重现起来很麻烦,您可以将结构参数化并使用 IO Int
或 Int
对其进行实例化。例如,假设您想要来自网络的字段 a
但 b
来自 IO monad:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
import Data.Aeson
import System.Random
import Data.ByteString.Lazy (ByteString)
data Example' a =
Example { a :: Int
, b :: a
} deriving (Show,Functor,Foldable,Traversable)
type Partial = Example' (IO Int)
type Example = Example' Int
instance FromJSON Partial where
parseJSON (Object o) =
Example <$> o .: "a"
<*> pure (randomRIO (1,10))
loadExample :: Partial -> IO Example
loadExample = mapM id
parseExample :: ByteString -> IO (Maybe Example)
parseExample = maybe (pure Nothing) (fmap Just . loadExample) . decode
注意 loadExample
如何使用我们的 traverse
实例来执行结构内的 IO 操作。这是一个使用示例:
Main> parseExample "{ \"a\" : 1111 }"
Just (Example {a = 1111, b = 5})
高级
如果您有不止一种类型的字段需要 IO 操作,您可以
为所有这些创建一种数据类型。您可以将
b
设为IO Int
,而不是IO MyComplexRecord
。这是简单的解决方案。更复杂有趣的解决方案是使用更高种类的类型参数。
对于选项 2,请考虑:
data Example' f = Example { a :: Int
, b :: f Int
, c :: f String }
然后您可以使用 Proxy
和 Control.Monad.Identity
来代替之前使用的 IO Int
和 Int
等值。您需要编写自己的遍历,因为您无法为此 class 推导 Traverse
(这就是我们上面使用的 mapM
的原因)。我们可以使用一些扩展(其中有 RankNTypes)用 kind (* -> *) -> *
进行遍历 class,但除非经常这样做,并且我们得到某种派生支持或 TH,我不认为值得。
这是另一个解决方案,它需要更多的体力劳动,但方法非常简单 - 生成一个随机数 IO Example
使用它来生成一个随机数 "parser"。解码成 JSON 是用通常的 decode
函数完成的。
{-# LANGUAGE OverloadedStrings #-}
module Test where
import Data.Aeson
import Data.Aeson.Types
import System.Random
data Example = Example {_a :: Int, _b :: Int} deriving (Show, Ord, Eq)
getExample :: IO (Value -> Maybe Example)
getExample = do
ex <- randomRIO (Example 1 1, Example 10 100)
let ex' = withObject "Example" $ \o ->
do a <- o .:? "a" .!= _a ex
b <- o .:? "b" .!= _b ex
return $ Example a b
return (parseMaybe ex')
instance Random Example where
randomRIO (low,hi) = Example <$> randomRIO (_a low,_a hi)
<*> randomRIO (_b low,_b hi)
...
main :: IO ()
main = do
getExample' <- getExample
let example = getExample' =<< decode "{\"a\": 20}"
print example
我不确定,但我相信这是@DanielWagner 解决方案的更详细的实现。