Ah. I’ve finally had occasion to use lenses in Haskell. For non-Haskell programmers, one use for them is where you might use properties, for instance in C#, or where you might find it convenient to pass around references to an object field. (Bartosz Milewski has a little on the theory behind them here.)

In my case, I’ve been Haskellifying some backup scripts I use, which read a config file (in .ini format) and execute some duplicity backup commands based on the contents.

My code doesn’t do anything terribly surprising — I created a datatype to represent duplicity options —

data DupConf = DupConf {
sourceDir :: Text
, destDir :: Text
, dupOptions :: [Text]
, niceLevel :: Int
, password :: Maybe Text
...
}

and I’ve used the ConfigFile package to parse the configuration file.

A defaultDupConf value holds default values for the various options:

defaultDupConf = DupConf {
sourceDir = error "must specify a sourceDir"
, destDir = error "must specify a destDir"
, dupOptions = []
, niceLevel = 0
, password = Nothing
...
}

To get all the options from the config file involves a fair amount of boilerplate, and it’s handy to be able to factor that out. The essential things you want to know are: for a particular option name, what section of the .ini file is it in? What field of a DupConf type does it correspond to (we’ll want a “getter” function, to get a default value from defaultDupConf, as well as a “setter” function, to set values as they’re read) ? And what conversion function do we need to apply to something read from the configuration file (usually a String) in order to store it in a DupConf record field (usually holding a Text).

In other languages, you usually end up creating a lookup table (or vector, map, etc.) showing for each property how to read it, convert it, store it, and so on. So I took the same approach in Haskell:

type FieldReader m a r =  ConfigParser -> C.SectionSpec
-> C.OptionSpec
-> a
-> m rdata FieldSpec m a r where
FieldSpec :: Monad m => 
FieldReader m a r -> String -> String -> Spec m a r
-- ... helper function mkReader elided ...
specs :: MonadError CPError m => [FieldSpec m DupConf DupConf]
specs = [
FieldSpec (mkReader Text.Pack sourceDir) "DEFAULT" "sourceDir"
, FieldSpec (mkReader Text.Pack destDir) "duplicity" "destDir"
, FieldSpec (mkReader fromCSV excludeDirs)"duplicity" "excludeDirs"
...
]

The “mkReader” function takes a conversion function, a lens — i.e., what would probably be a property or field reference, in an OO language — and returns a function which will read a property from the config file if it exists, and mutate that property in a DupConf object; or, if no such property exists in the config file, the value of defaultDupConf is used instead.

Anyway. Nothing amazing, I’ve just not had occasion to use lenses before.

Like what you read? Give phlummox a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.