Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Defining a proper interface for data types with many fields

Tags:

haskell

For the representation of a DSL syntax tree I have data types that represent this tree. At several places, within this tree I get quite a number of subelements that are optional and/or have a "*" multiplicity. So one data type might look something like

data SpecialDslExpression = MyExpression String [Int] Double [String] (Maybe Bool)

What I am looking for is a possibility to construct such a type without having to specify all of the parameters, assuming I have a valid default for each of them. The usage scenario is such that I need to create many instances of the type with all kinds of combinations of its parameters given or omitted (most of the time two or three), but very rarely all of them. Grouping the parameters into subtypes won't get me far as the parameter combinations don't follow a pattern that would have segmentation improve matters.

I could define functions with different parameter combinations to create the type using defaults for the rest, but I might end up with quite a number of them that would become hard to name properly, as there might be no possibility to give a proper name to the idea of createWithFirstAndThirdParameter in a given context.

So in the end the question boils down to: Is it possible to create such a data type or an abstraction over it that would give me something like optional parameters that I can specify or omit at wish?

like image 369
Mathias Weyel Avatar asked Sep 05 '13 03:09

Mathias Weyel


Video Answer


1 Answers

I would suggest a combinations of lenses and a default instance. If you are not already importing Control.Lens in half of your modules, now is the time to start! What the heck are lenses, anyway? A lens is a getter and a setter mashed into one function. And they are very composable. Any time you need to access or modify parts of a data structure but you think record syntax is unwieldy, lenses are there for you.

So, the first thing you need to do – enable TH and import Control.Lens.

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

The modification you need to do to your data type is adding names for all the fields, like so:

data SpecialDslExpression = MyExpression { _exprType :: String
                                         , _exprParams :: [Int]
                                         , _exprCost :: Double
                                         , _exprComment :: [String]
                                         , _exprLog :: Maybe Bool
                                         } deriving Show

The underscores in the beginning of the field names are important, for the following step. Because now we want to generate lenses for the fields. We can ask GHC to do that for us with Template Haskell.

$(makeLenses ''SpecialDslExpression)

Then the final thing that needs to be done is constructing an "empty" instance. Beware that nobody will check statically that you actually fill all the required fields, so you should preferably add an error to those fields so you at least get a run-time error. Something like this:

emptyExpression = MyExpression (error "Type field is required!") [] 0.0 [] Nothing

Now you are ready to roll! You cannot use an emptyExpression, and it will fail at run-time:

> emptyExpression
MyExpression {_exprType = "*** Exception: Type field is required!

But! As long as you populate the type field, you will be golden:

> emptyExpression & exprType .~ "Test expression"

MyExpression { _exprType = "Test expression"
             , _exprParams = []
             , _exprCost = 0.0
             , _exprComment = []
             , _exprLog = Nothing
             }

You can also fill several fields at once, if you want to.

> emptyExpression & exprType .~ "Test expression"
|                 & exprLog .~ Just False
|                 & exprComment .~ ["Test comment"]

MyExpression { _exprType = "Test expression"
             , _exprParams = []
             , _exprCost = 0.0
             , _exprComment = ["Test comment"]
             , _exprLog = Just False
             }

You can also use lenses to apply a function to a field, or look inside a field of a field, or modify any other existing expression and so on. I definitely recommend taking a look at what you can do!

like image 187
kqr Avatar answered Sep 29 '22 22:09

kqr