samedi 7 septembre 2019

How to use randomness in Haskell to produce instances of a JSON "model"?

I have this work where I have to read a JSON from a file and generate instances of it based on its model. I'm using aeson to serialize the objects, but I'm having a huge problem dealing with randomness to produce new objects.

Produce a new JSON based on what I get from file is pretty straight forward:

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}

import qualified Data.ByteString.Lazy.Char8    as ByteString
import qualified Data.Aeson                    as Aeson
import qualified Data.Aeson.Types              as Types
import qualified Data.Text

read :: String -> IO ()
read filePath = do
    json <- readFile filePath
    let Just parsedJSON =
            Data.Aeson.decode $ ByteString.pack json :: Maybe Aeson.Object
    let newJSON = fmap valueMapper parsedJSON
    print $ Aeson.encode newJSON


valueMapper :: Types.Value -> Types.Value
valueMapper value =
    case value of
        Types.String _      -> Types.String "randomValue"
        Types.Number _      -> Types.Number 0
        Types.Object object -> Types.Object $ fmap valueMapper object
        Types.Array  array  -> Types.Array $ fmap valueMapper array

My first attempt was to produce random values outside the IO. I used this function:

randomStr :: String
randomStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen

Putting it on valueMapper:

valueMapper :: Types.Value -> Types.Value
valueMapper value =
    case value of
        Types.String _      -> Types.String $ Data.Text.pack randomStr
        Types.Number _      -> Types.Number 0
        Types.Object object -> Types.Object $ fmap valueMapper object
        Types.Array  array  -> Types.Array $ fmap valueMapper array

This "works", but all generated strings are the same, for every String field.

After a little research, I found out that if I want to produce different values for each String occurrence, I have to use the IO:

randomStr :: IO String
randomStr = replicateM 10 (randomRIO ('a', 'z'))

Now, I know that I have different strings for each call of randomStr... But I also have a type mismatch. Aeson String constructor to Value takes a Data.Text, but what I have is an IO String. As far as I know, my strings can never come back from IO.

I don't know if there is a way (hope so) to use the latest randomStr to compose my new JSON object. I also don't know if my approach is a good one. I'm open to suggestions about how can I put this to work, in my or any other way (some tips on how to write better code would be awesome too).




Aucun commentaire:

Enregistrer un commentaire