Lenses and probability¶

In [1]:
:e BlockArguments
:e LambdaCase
:e OverloadedStrings

import Data.Aeson.Lens
import Control.Lens hiding (nth, (#))
import Data.Aeson
import Data.Maybe (fromMaybe)
import Control.Monad.Bayes.Class
import Control.Monad.Bayes.Sampler.Strict
import Control.Monad.Bayes.Weighted
import Data.Monoid 
import qualified Data.ByteString.Lazy as B
import Text.Pretty.Simple
import qualified Data.Text as T
import Control.Monad

Lenses are getters and setters, i.e. ways of extracting a value from inside a data structure or updating that value.

Because of their type in the Haskell lens library, namely forall f. (a -> f b) -> (c -> f d), we can specialize f to MonadDistribution m => m, and so use any lens to probabilistically update a data structure's elements in complex ways.

As an example of how easy and extensible this, we provide some examples using lenses to manipulate a JSON file. This relies on the aeson and lens-aeson packages.

In [2]:
json <- fromMaybe undefined . decode <$> B.readFile "../file.json" :: IO Value

pPrintCustom = pPrintOpt CheckColorTty defaultOutputOptionsNoColor {outputOptionsCompact = True, outputOptionsIndentAmount = 2} 
pPrintCustom json
Object
  ( fromList
    [
      ( "address"
      , Object
        ( fromList
          [ ( "id", Number 5.4 ), ( "streetAddress", String "21 2nd Street" ) ]
        )
      )
    ,
      ( "age", Number 27.0 )
    ,
      ( "height", Number 1.5 )
    ,
      ( "isAlive", Bool True )
    ,
      ( "name", String "John" )
    ]
  )

For example, if I have a function that randomly flips a boolean or a function that puts noise into a string, like

In [3]:
randomlyFlip :: MonadDistribution m => Bool -> m Bool
randomlyFlip True = bernoulli 0.01
randomlyFlip False = bernoulli 0.9
In [4]:
noisifyString :: MonadDistribution m => T.Text -> m T.Text
noisifyString t = fmap T.pack $ forM (T.unpack t) $ \letter -> do
    x <- bernoulli 0.2
    if x then uniformD "abcdefghijklmnopqrstuvwxyz" else return letter

then I can use it to update the "isAlive" value as follows, using the lens (_Object . traverse . _Bool) which points to the json object's list of element's boolean element:

In [5]:
pPrintCustom =<< sampleIOfixed ((_Object . traverse . _Bool) randomlyFlip json)
Object
  ( fromList
    [
      ( "address"
      , Object
        ( fromList
          [ ( "id", Number 5.4 ), ( "streetAddress", String "21 2nd Street" ) ]
        )
      )
    ,
      ( "age", Number 27.0 )
    ,
      ( "height", Number 1.5 )
    ,
      ( "isAlive", Bool False )
    ,
      ( "name", String "John" )
    ]
  )

Using the Plated instance of json values, I can do more powerful operations, like updating all doubles, bools and strings at arbitrarily nested positions in the json

In [6]:
jsonDist :: Distribution Value
jsonDist = 
    ((transformM . _Double) (\case x -> normal x 0.001) >=>
    (transformM . _Bool) randomlyFlip >=>
    (transformM . _String) noisifyString
    )
    json
In [7]:
pPrintCustom =<< sampleIOfixed (unweighted jsonDist)
Object
  ( fromList
    [
      ( "address"
      , Object
        ( fromList
          [
            ( "id", Number 5.398324403347256605911752558313310146331787109375 )
          ,
            ( "streetAddress", String "21 fnd Stredt" )
          ]
        )
      )
    ,
      ( "age", Number 27.001264842576262026341282762587070465087890625 )
    ,
      ( "height"
      , Number 1.5021398495523310412380624256911687552928924560546875
      )
    ,
      ( "isAlive", Bool False )
    ,
      ( "name", String "lohn" )
    ]
  )