How to handle multiple but similar post types

So, let's say I have the following nearly identical type declarations. The types are related to each other in that MyType2 is a "processed" version of MyType1.

data MyType1 = MyType1 {
   field1 :: Maybe Text
   manyOtherFields :: Maybe Whatever
}

data MyType2 = MyType2 {
  field1 :: Text,
  manyOtherFields :: Whatever
}

      

Initially, field1 is possible because the data comes from user input. Once processed, it becomes the Just value.

This Maybes processing scheme in Justs will likely repeat 10 or even 100 times throughout my program, with potentially many similar combinations describing variations of essentially the same object at different stages of processing.

How to avoid duplicate type definitions for all similar combinations?

Further explanation:

In my real program, the problem is accepting a file as input from a web form. When the form is received by my code, the file input field is Maybe FilePath, so I have a data type like:

data Media = Media {
  filePath :: Maybe FilePath
  altText :: Text,
}

      

After processing the input, I need a new data type:

data Media2 = Media2 {
  filePath :: FilePath
  altText :: Text,
  height :: Int,
  width :: Int
}

      

This seems ugly and impractical because patterns like this are repeated over and over again in my program. It is quite possible that I need Media3 (and 4), not to mention all the other entities and their variants.

+3


source to share


2 answers


I forgot the name of this technique ... however, here it is:

import Data.Maybe
import Data.Functor.Identity

data MyType f = MyType
   { field1 :: f Text
   , manyOtherFields :: f Whatever
   }
type MyType1 = MyType Maybe
type MyType2 = MyType Identity

      

The fee should be for the constructor to Identity

wrap the data when it's fully processed.



For example:

x :: MyType1  -- partially processed
x = MyType{field1 = Nothing, manyOtherFields = Just whatever}

y :: MyType2  -- fully processed
y = MyType{field1 = Identity someText, manyOtherFields = Identity whatever}

      

+4


source


You specify three types of data in your structure:

data Media = Media {
    mediaId :: Int
  , mediaName :: Text
  , mediaFilePath :: FilePath
  , mediaMimeType :: Text
  , mediaHash :: Text
  , mediaWidth :: Int
  , mediaHeight :: Int
  , mediaCreated :: UTCTime
  , mediaUpdated :: UTCTime
}
data Media2 = Media2 {
    mediaId :: Int
  , mediaName :: Text
  , mediaFilePath :: Maybe FilePath
  , mediaMimeType :: Text
  , mediaHash :: Text
  , mediaWidth :: Int
  , mediaHeight :: Int
  , mediaCreated :: UTCTime
  , mediaUpdated :: UTCTime
}
data Media3= Media3 {
    media3Id :: Int
  , media3Name :: Text
  , media3FilePath :: FilePath
  , media3NewFilePath :: Maybe FilePath
  , media3MimeType :: Text
  , media3Hash :: Text
  , media3Width :: Int
  , media3Height :: Int
  , media3Created :: UTCTime
  , media3Updated :: UTCTime
}

      

... and complain that they violate the DRY principle (and I agree!). One simple solution is to split the shared parts, this way:

data Metadata = Metadata
  { id :: Int
  , name :: Text
  , mimeType :: Text
  , hash :: Text
  , width :: Int
  , height :: Int
  , created :: UTCTime
  , updated :: UTCTime
  }

      

Then you have several parameters to parameterize the remaining bits. One option is to have type modifiers; eg:

data Located   a = Located   { location :: FilePath, locatedValue :: a }
data Motion    a = Motion    { oldLocation, newLocation :: FilePath, motionValue :: a }
data UILocated a = UILocated { uiField :: Maybe FilePath, uilocatedValue :: a }

      



so the old type Media

, for example, will now be Located Metadata

. Another choice is to have an amount type for locations:

data Location
    = OnDisk FilePath
    | Nowhere
    | Moving FilePath FilePath

      

Then you can use (Metadata, Location)

as your type for all three, or put the location in the box Metadata

. This loses some static checking, but can be handy in some situations.

However, the third option is to add a polymorphic field to the metadata type:

data Metadata a = Metadata
  { id :: Int
  , name :: Text
  , mimeType :: Text
  , hash :: Text
  , width :: Int
  , height :: Int
  , created :: UTCTime
  , updated :: UTCTime
  , extra :: a
  }

      

so your old type Media

, for example, will now be Metadata FilePath

and Media3

will Metadata (FilePath, Maybe FilePath)

.

+4


source







All Articles