gpt4 book ai didi

haskell - 在 Haskell 中,如何将函数限制为一种数据类型的唯一构造函数?

转载 作者:行者123 更新时间:2023-12-02 20:37:16 25 4
gpt4 key购买 nike

我不知道如何表达这个问题。假设我正在尝试传递 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") 等。
  • 创建大量单独的数据类型 VideoTmpFilePictureTmpFile 等。
  • 创建一个 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,因此类型化的 tmpfiles 列表将很有用)

这看起来正确吗?我更喜欢 DataKinds 的想法,因此如果我可以将它们作为值进行检查,或者事实证明这根本不需要,我会选择它。

最佳答案

你是对的:使用 -XDataKindsTmpFile Video -> FilePath 方法就可以工作。事实上,我认为这可能是该扩展的一个很好的应用程序。

{-# LANGUAGE DataKinds #-}

data TmpFile (a :: FileFormat) = TmpFile FilePath
deriving Show

videoPath :: TmpFile Video -> FilePath

您需要此扩展来编写 TmpFile Video 的原因是 FileFormat 的构造函数是从头开始值级别(因此仅存在在运行时),而 TmpFile 是类型级/编译时。

当然还有另一种生成类型级实体的方法:定义类型!

data Spreadsheet = Spreadsheet
data Picture = Picture
data Video = Video

data TmpFile a = TmpFile a FilePath
deriving Show

videoPath :: TmpFile Video -> FilePath

此类类型称为幻像类型。但实际上,它们有点像黑客,可以解决以前缺乏适当的类型级别值的问题,而 DataKinds 现在已经为我们提供了这些值。因此,除非您需要与旧编译器兼容,否则请使用 DataKinds!

另一种方法是在编译时强制执行文件类型,而只是明确表明函数是部分函数。

data TmpFile = TmpFile FileFormat FilePath
deriving Show

videoPath :: TmpFile -> Maybe FilePath
videoPath (TmpFile Video p) = p
videoPath _ = Nothing

事实上,这种方法很可能是更合理的方法,具体取决于您打算做什么。

关于haskell - 在 Haskell 中,如何将函数限制为一种数据类型的唯一构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35279100/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com