如何处理多个但类似的记录类型

时间:2015-05-15 15:03:54

标签: haskell

所以,让我说我有以下几乎相同类型的声明。这些类型彼此相关,因为MyType2是一个"处理过的" MyType1的版本。

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

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

最初field1是一个Maybe,因为数据来自用户输入。一旦处理,它就变成了Just值。

这种将Maybes加工成Justs的模式可能会在整个程序中重复10次甚至100次,可能会有很多相似的组合描述不同处理阶段基本相同的实体的变化。 / p>

如何避免重复所有类似组合的类型定义?

进一步解释:

在我的实际程序中,问题是接受来自Web表单的输入文件。当我的代码收到表单时,文件输入字段是一个Maybe FilePath,所以我有一个类似的数据类型:

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

输入完成后,我需要一种新的数据类型:

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

这似乎是丑陋和不切实际的,因为类似的模式会在我的程序中反复重复。我很可能需要Media3(和4),更不用说所有其他实体及其变体了。

2 个答案:

答案 0 :(得分:4)

我忘记了这种技术的名称......但是,这里是:

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

付出的代价是让Identity构造函数在数据完全处理后进行包装。

例如:

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

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

答案 1 :(得分:4)

您在要点中提供了三种数据类型:

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
}

...并抱怨这些违反DRY原则(我同意!)。一个简单的解决方案是拆分共享部分,因此:

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

然后你有一些参数化剩余位的选项。一种选择是使用类型修饰符;例如:

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 }

因此旧版Media类型现在为Located Metadata。另一种选择是为地点设置总和类型:

data Location
    = OnDisk FilePath
    | Nowhere
    | Moving FilePath FilePath

然后,您可以使用(Metadata, Location)作为所有三种类型,或将该位置放在Metadata字段中。这会丢失一些静态检查,但在某些情况下可能会很方便。

第三种选择是在元数据类型中添加多态字段:

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

因此,旧的Media类型现在为Metadata FilePathMedia3Metadata (FilePath, Maybe FilePath)