2015-05-15 40 views
3

所以我们可以说我有以下的几乎相同的类型声明。这些类型相互关联,因为MyType2是MyType1的“已处理”版本。如何处理多个但是相似的记录类型

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

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

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

处理Maybes的这种模式到甫一很可能将被重复10的或时间甚至100的整个我的程序与描述的不同的处理阶段基本上相同的实体的变化潜在的许多类似的组合。

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

进一步说明:

以我实际的程序,问题是接受一个文件作为从万维网形式输入。当通过我的代码收到的形式将文件输入字段是一个可能的文件路径,所以我有这样一个数据类型:

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

一旦输入已经被处理我需要一个新的数据类型:

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

这似乎丑陋的和不切实际的,因为类似的模式将在我的程序重复一遍又一遍。很有可能我需要Media3(和4)以及其他所有实体及其变体。

回答

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} 
+0

谢谢,但...我的问题比这更复杂。 在许多其他领域,有些将会是Maybe,有些不会 - 在程序的不同阶段。我试图避免为每个可能的变体创建许多近克隆。 – Simon

+0

@Simon你可以尝试[点菜数据类型](http://www.cs.nott.ac.uk/~wss/Publications/DataTypesALaCarte.pdf)的方法。 – bheklilr

+1

@Simon我担心你需要一个参数很多的类型,它实际上是一个元组(可能是另一个名字)。 – chi

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,并Media3Metadata (FilePath, Maybe FilePath)

+0

这非常有帮助,谢谢! – Simon

相关问题