考虑以下片段:
data File
= NoFile
| FileInfo {
path :: FilePath,
modTime :: Data.Time.Clock.UTCTime
}
| FileFull {
path :: FilePath,
modTime :: Data.Time.Clock.UTCTime,
content :: String
}
deriving Eq
这种复制有点像“疣”,尽管在这种一次性的例子中并不是特别痛苦。为了进一步提高我对Haskell丰富类型系统的理解,可能首选“干净”/“惯用”方法来重构其他而不是 只需创建一个单独的{{ 1}} 2个重复字段的记录类型(然后将其替换为新data
类型的单个字段)或将data
记录符号替换为{{1}这也不是很干净(因为这里只需要FileFull
,而不是| FileFull File String
)?
(这两种“天真”的方法对于必须在这里的其余代码库中手动修复许多模块而言会有点干扰/烦恼。)
我考虑的一件事就是参数化:
FileInfo
然后对于那些“只是信息,未加载”的上下文NoFile
将为data File a
= NoFile
| FileMaybeWithContent {
path :: FilePath,
modTime :: Data.Time.Clock.UTCTime
content :: a
}
deriving Eq
,否则为a
。无论如何,似乎太笼统,我们要么()
或者没有,要么引导我们String
,再次使用String
参数。
当然我们以前去过那里: (是的,我可能应该删除Maybe
当然可以用a
完成,然后“重构任何编译错误”和“完成”。那可能是一天的顺序,但是知道Haskell和许多时髦的GHC扩展.. 谁知道 什么异乎寻常的理论技巧/公理/法律我是失踪了,对吧?!请参阅,“只是元数据信息”值和“具有元信息的文件内容”值之间的不同命名的“语义insta-differentialator”在代码库的其余部分中确实能够很好地理解。 / p>
content
并在整个过程中使用Maybe String
,但是......不确定是否确实存在实体理由完全是一个不同的问题..)
答案 0 :(得分:4)
以下所有内容都是等效/同构的,我认为您已经发现:
data F = U | X A B | Y A B C
data F = U | X AB | Y AB C
data AB = AB A B
data F = U | X A B (Maybe C)
所以自行车棚的颜色实际上取决于具体情况(例如你是否在其他地方使用AB
?)以及你自己的审美偏好。
它可能会澄清事情并帮助您了解您正在做些什么来理解the algebra of algebraic data types
我们称Either
类型为“和类型”类型和(,)
类型“类型”等类型,它们受制于您熟悉的相同类型的转换,例如分解
f = 1 + (a * b) + (a * b * c)
= 1 + ((a * b) * ( 1 + c))
答案 1 :(得分:3)
正如其他人所说,NoFile
构造函数可能不是必需的,但如果需要,可以保留它。如果您觉得您的代码更易读和/或更好理解,那么我说保留它。
现在结合其他两个构造函数的技巧是隐藏content
字段。通过参数化File
,您走在了正确的轨道上,但仅此一点是不够的,因此我们可以File Foo
,File Bar
等。幸运的是,GHC有一些很好的方法来帮助我们
我会在这里写出代码,然后解释它是如何工作的。
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
import Data.Void
data Desc = Info | Full
type family Content (a :: Desc) where
Content Full = String
Content _ = Void
data File a = File
{ path :: FilePath
, modTime :: UTCTime
, content :: Content a
}
这里有一些事情发生。
首先请注意,在File
记录中,content
字段现在的类型为Content a
,而不仅仅是a
。 Content
是一个类型系列,(在我看来)是类型级函数的混淆名称。也就是说,编译器根据Content a
是什么以及我们如何定义a
来替换其他类型的Content
。
我们将Content Full
定义为String
,因此当我们有值f1 :: File Full
时,其内容字段将具有String
值。另一方面,f2 :: File Info
将有content
字段,类型为Void
,没有值。
酷吧?但是什么阻止我们现在拥有File Foo
?
这就是DataKinds
来救援的地方。它将数据类型Desc
“提升”为一种类型(Haskell中的类型类型),并将类型构造函数Info
和Full
“提升为类型Desc
而不是类型Desc
仅仅是Content
类型的值。
请注意a
我已注释a
的声明。它看起来像一个类型注释,但a
已经是一个类型。这是一种注释。它强制Desc
成为善意的Desc
,而Info
种Full
只有File
和File
。
到现在为止你可能已经完全卖掉了,但是我应该警告你没有免费的午餐。特别是,这是一个 compile -time构造。您的单File Info
类型会变成两种不同的类型。这可能导致其他相关逻辑(File Full
记录的生产者和消费者)变得复杂。如果您的用例不会将File
条记录与content
条记录混合在一起,那么这就是您的选择。另一方面,如果你想做一些像Maybe String
个记录的列表,这些记录可以是两种类型的混合,那么你最好只是制作File Info
字段的类型{ {1}}。
另一件事是,您如何制作Void
,因为content
字段没有undefined
的价值?好吧,从技术上来说,使用error "this should never happen"
或Void -> a
应该没问题,因为(道德上)不可能有Void
类型的函数,但如果这让你感到不安(可能应该),然后用()
替换>>>first = ['hello', 'hey', 'hi', 'hey']
>>>second = ['hey', 'hi', 'hello']
。单位几乎没用,也不需要底部的“值”。