【发布时间】:2016-05-18 16:10:18
【问题描述】:
我不知道如何措辞这个问题。假设我正在尝试传递 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 a 之外,它们都允许您创建嵌套类型TmpFile (ListOf 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
(这似乎是一件奇怪的事情,但我的实际程序将是一种小型语言的解释器,它编译为 Shake 规则,每个变量对应一个 tmpfile,因此类型化的 tmpfile 列表将很有用)
这看起来对吗?我更喜欢DataKinds 的想法,所以如果我可以将它们作为值进行检查,或者事实证明这根本不需要,我会选择它。
【问题讨论】:
-
这在一定程度上取决于您想要做什么-例如,对于我从您的问题中看到的内容,我确实会选择简单的(单独的)
newtype包装器,例如newtype VideoPath = VideoPath FilePath -
或采用 幻像类型 方法:
data TempFile a = TempFile FilePath和data Videoformat(需要 GHC 扩展EmptyDataDecls)然后let videoFile = TempFile myPath :: TempFile Videoformat...
标签: haskell types type-systems