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

Haskell 如何处理多个但相似的记录类型,haskell,Haskell,假设我有以下几乎相同的类型声明。这些类型相互关联,因为MyType2是MyType1的“已处理”版本 data MyType1 = MyType1 { field1 :: Maybe Text manyOtherFields :: Maybe Whatever } data MyType2 = MyType2 { field1 :: Text, manyOtherFields :: Whatever } 最初,field1是一个变量,可能是因为数据来自用户输入。然而,一旦

假设我有以下几乎相同的类型声明。这些类型相互关联,因为MyType2是MyType1的“已处理”版本

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
将事实编码到数据类型中。这有助于避免空指针错误,这在更传统的语言中是一个严重的问题。