2017-10-18 76 views
2

我有很多字段的數據類型,如果不是由JSON配置文件手動指定,應隨機設置。我正在使用Aeson來解析配置文件。做這個的最好方式是什麼?如何從IO使用解析器從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單子或線程分析器沿着一些隨機生成,最理想的是採用埃宋?

回答

4

好了,我不知道這是否是一個好主意,雜耍額外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))) 

在每最後兩行,第一個pureInt -> IO Int,第二個是IO Int -> Parser (IO Int)。在ghci的:

> sequence (decode "{}") :: IO (Maybe Example) 
Just (Example {a = 4, b = 6}) 
+0

正如@danidiaz所提到的,這基本上是對'Compose Parser IO'使用'Applicative'和'Alternative'實例。人們可以直接使用這種類型,或者將其用作在更大的開發中編寫此類代碼的進一步指導。 –

2

我不知道一個很好的策略來獲得你想要成爲自ParseJSON單子不是一個變壓器或基於IO。你可以更容易地做的是解碼成一種類型,然後翻譯成第二種,如前面的問題'Give a default value for fields not available in json using aeson'所做的那樣。

由於大型結構的重現可能很麻煩,因此您可以使用IO IntInt對結構進行參數化和實例化。例如,假設您從IO單子想現場a從電線但b隨機:

{-# 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動作,你既可以

  1. 使一種數據類型爲他們所有。 b不是IO Int,而是。這是簡單的解決方案。

  2. 更復雜和有趣的解決方案是使用更高類型的參數。

對於選項2,請考慮:

data Example' f = Example { a :: Int 
          , b :: f Int 
          , c :: f String } 

然後,您可以使用ProxyControl.Monad.Identity不是值的像IO IntInt以前使用。你需要編寫自己的遍歷,因爲你不能得到這個類的Traverse(這是給我們上面使用的mapM)。我們可以用類似(* -> *) -> *的遍歷類來使用一些擴展(其中的RankNType),但除非經常這樣做,並且我們得到某種派生支持或TH,我認爲這不值得。

+0

'ParseJSON monad不是變壓器或基於IO'。但它是一個'應用','應用'構成。也許一個基於'Data.Functor.Compose IO Parser'的解決方案可以工作。 – danidiaz

+0

當然,我也很想看到這個答案。 –

+1

@danidiaz事實上,在我的回答中,我只使用'Applicative'組合器,並且基本上使用Compose'實例(但沒有'Compose' newtype包裝器)。但是直到你指出它,我才意識到這一點,所以這是在更大的開發中編寫代碼的一個很好的見解和指導! –

0

這是另一個解決方案,它涉及更多的手工勞動,但方法非常簡單 - 生成一個隨機的IO Example用它來生成一個隨機的「解析器」。解碼爲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解決方案的更詳細的實現。

+0

回覆:「我相信這是@ DanielWagner解決方案的更詳細的實現」,我認爲我們的方法略有不同(儘管這兩者都很有趣);你做一些'IO'來生成一個解析器,而我做一些解析來生成一個'IO'動作。 –

+0

這是一個有趣的分解方式。我們有三個答案歸結爲「解析並生成需要執行的IO操作」,「執行IO以生成解析器」以及「解析以生成具有嵌入式IO操作的對象以供執行」。 –