我不确定如何说出这个问题。假设我试图传递tmpfiles的路径,我想捕获有不同格式的tmpfile的想法,并且每个函数仅适用于其中一个。这有效:
data FileFormat
= Spreadsheet
| Picture
| Video
deriving Show
data TmpFile = TmpFile FileFormat FilePath
deriving Show
videoPath :: TmpFile -> FilePath
videoPath (TmpFile Video p) = p
videoPath _ = error "only works on videos!"
但是必须有更好的方法来编写没有运行时错误的权利吗?我想到了两个选择,这个:
type TmpSpreadsheet = TmpFile Spreadsheet
type TmpPicture = TmpFile Picture
type TmpVideo = TmpFile Video
videoPath :: TmpVideo -> FilePath
或者这个:
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
但显然他们没有编译。这样做的正确方法是什么?其他一些想法,没有特别吸引人的:
TmpFile
而不是相反,因此值为Video (TmpFile "test.avi")
等。VideoTmpFile
,PictureTmpFile
等。TmpFile
类型类我还考虑过学习-XDataKinds
扩展,但怀疑我错过了一些更简单的东西,没有它就可以完成。
DataKinds
和幻像类型,它们具有可以用另一个扩展名删除的虚拟值构造函数),它们都可以工作!然后我试着再往前走一点。除常规TmpFile (ListOf a)
外,它们都允许您创建嵌套类型TmpFile a
,这很酷。但是我暂时决定使用普通的幻像类型(完整的值构造函数),因为你可以对它们进行模式匹配。例如,我很惊讶这实际上有效:
data Spreadsheet = Spreadsheet deriving Show
data Picture = Picture deriving Show
data Video = Video deriving Show
data ListOf a = ListOf a deriving Show
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
videoPath (TmpFile Video p) = p
-- read a file that contains a list of filenames of type a,
-- and return them as individual typed tmpfiles
listFiles :: TmpFile (ListOf a) -> IO [TmpFile a]
listFiles (TmpFile (ListOf fmt) path) = do
txt <- readFile path
let paths = map (TmpFile fmt) (lines txt)
return paths
vidPath :: TmpFile Video
vidPath = TmpFile Video "video1.txt"
-- $ cat videos.txt
-- video1.avi
-- video2.avi
vidsList :: TmpFile (ListOf Video)
vidsList = TmpFile (ListOf Video) "videos.txt"
main :: IO [FilePath]
main = do
paths <- listFiles vidsList -- [TmpFile Video "video1.avi",TmpFile Video "video2.avi"]
return $ map videoPath paths -- ["video1.avi","video2.avi"]
据我所知,与DataKinds
的等效内容非常相似,但无法作为值访问fmt
:
{-# LANGUAGE DataKinds, KindSignatures #-}
data FileFormat
= Spreadsheet
| Picture
| Video
| ListOf FileFormat
deriving Show
data TmpFile (a :: FileFormat) = TmpFile FilePath
deriving Show
vidPath :: TmpFile Video
vidPath = TmpFile "video.avi"
vidsList :: TmpFile (ListOf Video)
vidsList = TmpFile "videos.txt"
videoPath :: TmpFile Video -> FilePath
videoPath (TmpFile p) = p
listFiles :: TmpFile (ListOf a) -> IO [TmpFile a]
listFiles (TmpFile path) = do
txt <- readFile path
let paths = map TmpFile (lines txt)
return paths
main :: IO [FilePath]
main = do
paths <- listFiles vidsList
return $ map videoPath paths
(这似乎是一个奇怪的事情,但我的实际程序将成为一个小语言的解释器,使用对应于每个变量的tmpfile编译Shake规则,因此键入的tmpfiles列表将很有用)
这看起来是对的吗?我更喜欢DataKinds
的想法,所以如果我能将它们视为价值观,或者如果事实证明它永远不需要,我会选择它。
答案 0 :(得分:5)
您是对的:使用-XDataKinds
,TmpFile Video -> FilePath
方法可行。事实上,我认为这可能是该扩展的一个很好的应用。
{-# LANGUAGE DataKinds #-}
data TmpFile (a :: FileFormat) = TmpFile FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
您需要此扩展名来编写TmpFile Video
的原因是FileFormat
的构造函数是ab initio value-level (因此仅在运行时存在),而{{ 1}}是类型级/编译时。
当然还有另一种生成类型级实体的方法:定义类型!
TmpFile
此类型称为幻像类型。但实际上,他们可以解决以前缺乏正确的类型级别值的问题,而DataKinds现在给了我们这些价值。因此,除非您需要与旧编译器兼容,否则请使用DataKinds!
另一种方法是不在编译时强制执行文件类型,而只是明确表示函数是部分的。
data Spreadsheet = Spreadsheet
data Picture = Picture
data Video = Video
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
事实上,这种方法可能更合理,取决于你计划做什么。
答案 1 :(得分:2)
首先,我建议不要使用像“DataKinds”这样的奇特扩展,除非你绝对需要它们。原因非常实用和通用:用于解决问题的语言概念越多,推理代码的难度就越大。
此外,“DataKinds”并不是一个容易理解的概念。它是一个同时跨越两个宇宙的过渡概念:价值观和类型。就个人而言,我觉得它很有争议,只有在没有其他选择时才会应用它。
在您的情况下,您已经找到了两种更简单地解决问题的方法,没有“DataKinds”:
以格式而不是相反的方式包装TmpFile,因此值为Video(TmpFile“test.avi”)等。
制作大量独立的数据类型VideoTmpFile,PictureTmpFile等。
我特别喜欢包装类型的想法,因为它具有灵活性和可组合性。以下是我如何积累它:
newtype Video a =
Video a
deriving (Functor, Foldable, Traversable)
newtype Picture a =
Picture a
deriving (Functor, Foldable, Traversable)
videoPath :: Video FilePath -> FilePath
您可以注意到两件事:
Video
和Picture
是一般概念,它们不仅仅与您的临时文件绑定,而且它们已经实现了一些标准接口。这意味着它们可以重复用于其他目的。
Video
和Picture
的定义中有明显的模式。
您在Video
和Picture
中看到的模式可以称为“优化类型”,并在the "refined" package等中抽象出来。所以你可能会对此感兴趣。
至于你的其他选择:
制作TmpFile类型类
在任何地方使用部分函数,但添加保护函数以抽象模式匹配
两者都是明确的“否”。不要繁殖类型类,留下它们是真正的一般概念,它们有法律和可能的(类别)理论。该语言为您提供了丰富的其他抽象方法。另外,不要让部分函数爬到你的API中 - 在社区中已经达成共识,认为它是反模式。