Looking at an answer to this question:
https://stackoverflow.com/a/34164251/1052117
I see that it defines a data type that is used to parse a JSON object.
data Address = Address
{ house :: Integer
, street :: String
, city :: String
, state :: Maybe String
, zip :: String -- here I change the original, zip codes are strings, they have leading zeros.
} deriving (Show, Eq)
$(deriveJSON defaultOptions ''Address)
This is helpful, but I wonder: How could I change the Address data type to have all json fields be nullable? Specifically I see a Maybe before the state field, but I'm imagining a larger data structure where it would be tedious to modify all of the fields to Maybe fields. For example, while I /could/ re-write above as:
data Address = Address
{ house :: Maybe Integer
, street :: Maybe String
, city :: Maybe String
, state :: Maybe String
, zip :: Maybe String
} deriving (Show, Eq)
What function could I apply to the Address data type /in code/ to achieve this same result without rewriting all the code and manually inserting the Maybes?
The main benefit of this is that it creates functions that lookup fields in the data type. By using record syntax to create this data type, Haskell automatically made these functions: firstName, lastName, age, height, phoneNumber and flavor. There's another benefit to using record syntax.
In Haskell, you can have many constructors for your data type, separated by a vertical bar |. Each of your constructors then has its own list of data types! So different constructors of the same type can have different underlying data! We refer to a type with multiple constructors as a “sum” type.
So it’s not surprising that Haskell has some nifty constructs for building our own types. Constructors and sum types give us the flexibility to choose what kind of data we want to store. We can even change the data stored for different elements of the same type!
However, it's a very strong convention in Haskell to never add typeclass constraints in data declarations. Why? Well, because we don't benefit a lot, but we end up writing more class constraints, even when we don't need them.
As is discussed in the comments, using a functor-functor would work for this with just very small changes to the original data type.
If you start out with
data Address = Address
{ house :: Integer
, street :: String
, city :: String
, state :: Maybe String
, zip :: String
} deriving (Show, Eq)
then it is equivalent to
import Data.Functor.Identity
data AddressF f = Address
{ house :: f Integer
, street :: f String
, city :: f String
, state :: Maybe String
, zip :: f String
} deriving (Show, Eq)
type Address = AddressF Identity
and then you can get the second one by writing
type Address' = AddressF Maybe
To get back to the original definition, you can write
toOriginal (AddressF (Identity house) (Identity street) (Identity city) mbState (Identity zip)) = Address house street city mbState zip
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With