Haskell 如何处理多个但相似的记录类型
假设我有以下几乎相同的类型声明。这些类型相互关联,因为MyType2是MyType1的“已处理”版本Haskell 如何处理多个但相似的记录类型,haskell,Haskell,假设我有以下几乎相同的类型声明。这些类型相互关联,因为MyType2是MyType1的“已处理”版本 data MyType1 = MyType1 { field1 :: Maybe Text manyOtherFields :: Maybe Whatever } data MyType2 = MyType2 { field1 :: Text, manyOtherFields :: Whatever } 最初,field1是一个变量,可能是因为数据来自用户输入。然而,一旦
data MyType1 = MyType1 {
field1 :: Maybe Text
manyOtherFields :: Maybe Whatever
}
data MyType2 = MyType2 {
field1 :: Text,
manyOtherFields :: Whatever
}
最初,field1是一个变量,可能是因为数据来自用户输入。然而,一旦处理,它就成为一个公正的价值
在我的程序中,这种处理为just的模式可能会重复10次甚至100次,可能有许多类似的组合,描述不同处理阶段基本相同实体的变化
如何避免对所有类似的组合重复类型定义
进一步解释:
在我的实际程序中,问题是接受来自web表单的文件作为输入。当我的代码收到表单时,文件输入字段是一个文件路径,因此我有一个数据类型,如:
data Media = Media {
filePath :: Maybe FilePath
altText :: Text,
}
处理完输入后,我需要一个新的数据类型:
data Media2 = Media2 {
filePath :: FilePath
altText :: Text,
height :: Int,
width :: Int
}
data Metadata a = Metadata
{ id :: Int
, name :: Text
, mimeType :: Text
, hash :: Text
, width :: Int
, height :: Int
, created :: UTCTime
, updated :: UTCTime
, extra :: a
}
这看起来既丑陋又不切实际,因为类似的模式将在我的程序中反复出现。很可能我需要一个媒体3(和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
所要付出的代价是在数据完全处理后有一个标识
构造函数包装数据
例如:
x :: MyType1 -- partially processed
x = MyType{field1 = Nothing, manyOtherFields = Just whatever}
y :: MyType2 -- fully processed
y = MyType{field1 = Identity someText, manyOtherFields = Identity whatever}
您在要点中给出了三种数据类型:
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
}
…并抱怨这些违反了干燥原则(我也同意!)。一个简单的解决方案是拆分共享部分,因此:
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 }
例如,旧的媒体
类型现在将是定位元数据
。另一种选择是为地点设置一个总和类型:
data Location
= OnDisk FilePath
| Nowhere
| Moving FilePath FilePath
然后,您可以使用(元数据,位置)
作为这三种类型的类型,或者将位置放在元数据的字段中。这会丢失一些静态检查,但在某些情况下可能很方便
第三种选择是向元数据类型添加多态字段:
data Media2 = Media2 {
filePath :: FilePath
altText :: Text,
height :: Int,
width :: Int
}
data Metadata a = Metadata
{ id :: Int
, name :: Text
, mimeType :: Text
, hash :: Text
, width :: Int
, height :: Int
, created :: UTCTime
, updated :: UTCTime
, extra :: a
}
例如,旧的媒体
类型现在将是元数据文件路径
,而媒体3
将是元数据(文件路径,可能是文件路径)
,谢谢,但是。。。我的问题比那更复杂。在许多其他领域中,有些可能是,有些不会——在项目的不同阶段。我试图避免为每个可能的变体创建许多近似克隆。@Simon您可以尝试这种方法。@Simon我担心您需要一个参数化程度很高的类型,它实际上是一个元组(可能是另一个名称).@bheklillr-链接断开:/@Simon您是否试图避免使用可能是因为您习惯于在其他语言中使用空值?当您使用由Functor
、Applicative
和Monad
以及数据中的组合符提供的工具时,在Haskell中使用Maybe
值并不困难。Maybe
。是的,现实世界中充满了空值,这就是为什么Haskell的类型系统允许您使用Maybe
将事实编码到数据类型中。这有助于避免空指针错误,这在更传统的语言中是一个严重的问题。