在Haskell中,如何将函数限制为一个数据类型的一个构造函数?
我不知道这个问题该怎么说。假设我正在尝试传递tmpfile的路径,我想捕捉一个想法,即tmpfile有不同的格式,每个函数只在其中一个上工作。这项工作:在Haskell中,如何将函数限制为一个数据类型的一个构造函数?,haskell,types,type-systems,Haskell,Types,Type Systems,我不知道这个问题该怎么说。假设我正在尝试传递tmpfile的路径,我想捕捉一个想法,即tmpfile有不同的格式,每个函数只在其中一个上工作。这项工作: data FileFormat = Spreadsheet | Picture | Video deriving Show data TmpFile = TmpFile FileFormat FilePath deriving Show videoPath :: TmpFile -> FilePath videoP
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
- 制作一个
typeclassTmpFile
- 到处使用分部函数,但添加保护函数来抽象模式匹配
-XDataKinds
扩展,但我怀疑我错过了一些没有它可以做的简单得多的事情
编辑:我今天学到了很多!我尝试了下面概述的两种方法(datatypes
和phantom类型,它们都有虚拟值构造函数,可以通过另一个扩展删除),它们都可以工作!然后我试着再往前走一点。除了常规的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"]
(这似乎是一件奇怪的事情,但我的实际程序将是一种小型语言的解释器,该语言使用对应于每个变量的tmpfile编译抖动规则,因此tmpfiles的类型化列表将非常有用)
这看起来对吗?我更喜欢
数据种类
的想法,因此如果我可以将它们作为值进行检查,或者如果结果证明这是不需要的,我会选择它。你是对的:有了-xdatacates
,TmpFile Video->FilePath
方法就行了。事实上,我认为这可能是一个很好的扩展应用
{-# LANGUAGE DataKinds #-}
data TmpFile (a :: FileFormat) = TmpFile FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
编写TmpFile Video
时需要此扩展的原因是FileFormat
的构造函数是初始值级别(因此仅在运行时存在),而TmpFile
是类型级别/编译时
当然,还有另一种生成类型级实体的方法:定义类型
这种类型称为幻影类型。但实际上,它们在解决DataTypes现在提供给我们的前一个缺乏适当类型级别值的问题上有点笨拙。因此,除非您需要与旧编译器兼容,否则一定要使用DataTypes
另一种方法是不在编译时强制执行文件类型,而只是明确表示函数是部分的
data TmpFile = TmpFile FileFormat FilePath
deriving Show
videoPath :: TmpFile -> Maybe FilePath
videoPath (TmpFile Video p) = p
videoPath _ = Nothing
事实上,这种方法可能更合理,这取决于您计划做什么。首先,我建议不要使用诸如“数据类型”之类的外来扩展,除非您绝对需要它们。原因是非常实际和普遍的:解决问题所使用的语言概念越多,就越难对代码进行推理 此外,“数据种类”并不是一个容易理解的概念。这是一个同时跨越两个宇宙的过渡概念:价值观和类型。就我个人而言,我觉得它很有争议,只有在我没有其他选择的时候才会使用它 在您的案例中,您已经找到了两种更简单的解决问题的方法,没有“数据类型”:
- 将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
是通用概念,它们不仅绑定到临时文件,而且已经实现了一些标准接口。这意味着它们可以用于其他目的视频
和图片
的定义中有一个明显的模式您在
视频
和图片
中看到的模式可以称为“细化类型”,并从中抽象出来。所以你可能对此感兴趣
至于你的其他选择:
- 创建一个tmpfiletypeclass
- 到处使用分部函数,但添加保护函数来抽象模式匹配
这对双方都是明确的“不”。不要繁衍类型类,让它们成为真正的一般概念,它们背后有规律和可能的(范畴)理论。这种语言为你提供了丰富的其他抽象方法。另外,不要让部分函数爬到您的API上-社区一致认为这是一种反模式。这有点取决于您想做什么-例如,从您的问题中可以看出,我确实会选择简单(单独)
newtype
像newtype VideoPath=VideoPath FilePath
这样包装,或者采用幻影类型的方法:data TempFile a=TempFile FilePath
带有data Videoformat
(需要GHC扩展名EmptyDataDecls
),然后让videoFile=TempFile myPath::TempFile Videoformat
。。。“幻影类型”通常指的是一个类型构造函数,它由一个不使用的类型变量参数化。@DerekElkins:我想它指的是一些类型中没有实际使用的参数。我同意,我会说“幻影类型变量”而不仅仅是“幻影类型”,但是
data TmpFile = TmpFile FileFormat FilePath
deriving Show
videoPath :: TmpFile -> Maybe FilePath
videoPath (TmpFile Video p) = p
videoPath _ = Nothing
newtype Video a =
Video a
deriving (Functor, Foldable, Traversable)
newtype Picture a =
Picture a
deriving (Functor, Foldable, Traversable)
videoPath :: Video FilePath -> FilePath