Jef*_*eff 10 haskell types type-systems
我不确定如何说出这个问题.假设我正在尝试传递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!"
Run Code Online (Sandbox Code Playgroud)
但是必须有更好的方法来编写没有运行时错误的权利吗?我想到了两个选择,这个:
type TmpSpreadsheet = TmpFile Spreadsheet
type TmpPicture = TmpFile Picture
type TmpVideo = TmpFile Video
videoPath :: TmpVideo -> FilePath
Run Code Online (Sandbox Code Playgroud)
或这个:
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
Run Code Online (Sandbox Code Playgroud)
但显然他们没有编译.这样做的正确方法是什么?其他一些想法,没有特别吸引人:
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"]
Run Code Online (Sandbox Code Playgroud)
据我所知,等效与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
Run Code Online (Sandbox Code Playgroud)
(这似乎是一个奇怪的事情,但我的实际程序将成为一个小语言的解释器,使用对应于每个变量的tmpfile编译Shake规则,因此键入的tmpfiles列表将很有用)
那似乎对吗?我喜欢DataKinds
更好的想法,所以如果我能将它们视为价值观,或者如果事实证明这是永远不需要的话,我会选择它.
你是对的:有了-XDataKinds
,这种TmpFile Video -> FilePath
方法可行.事实上,我认为这可能是该扩展的一个很好的应用.
{-# LANGUAGE DataKinds #-}
data TmpFile (a :: FileFormat) = TmpFile FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
Run Code Online (Sandbox Code Playgroud)
您需要此扩展写入的原因TmpFile Video
是构造函数FileFormat
是ab initio 值级别(因此仅存在于运行时),而TmpFile
类型级别/编译时.
当然还有另一种生成类型级实体的方法:定义类型!
data Spreadsheet = Spreadsheet
data Picture = Picture
data Video = Video
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
Run Code Online (Sandbox Code Playgroud)
这种类型称为幻像类型.但实际上,对于前者缺乏适当的类型级别值而言,他们是一个很好的解决方法,而DataKinds现在给了我们这些价值.因此,除非您需要与旧编译器兼容,否则请使用DataKinds!
另一种方法是在编译时不强制执行文件类型,而只是明确表示函数是部分的.
data TmpFile = TmpFile FileFormat FilePath
deriving Show
videoPath :: TmpFile -> Maybe FilePath
videoPath (TmpFile Video p) = p
videoPath _ = Nothing
Run Code Online (Sandbox Code Playgroud)
事实上,这种方法可能更合理,取决于你计划做什么.
归档时间: |
|
查看次数: |
752 次 |
最近记录: |