所以,让我说我有以下几乎相同类型的声明。这些类型彼此相关,因为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),更不用说所有其他实体及其变体了。
答案 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 FilePath
,Media3
为Metadata (FilePath, Maybe FilePath)
。