在Haskell中,如何将函数限制为只有一个数据类型的构造函数? [英] In Haskell, how do you restrict functions to only one constructor of a data type?

查看:86
本文介绍了在Haskell中,如何将函数限制为只有一个数据类型的构造函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不知道如何说出这个问题。假设我试图传递tmpfiles的路径,并且想要捕获tmpfile有不同格式的想法,并且每个函数仅适用于其中的一个。这可以工作:

  data数据文件格式
=电子表格
|图片
|视频
派生显示

数据TmpFile = TmpFile FileFormat FilePath
派生显示

videoPath :: TmpFile - > FilePath
videoPath(TmpFile Video p)= p
videoPath _ =错误仅适用于视频!

但是,必须有更好的方法来编写它,而不会出现运行时错误?我想到了两种替代方案:

  type TmpSpreadsheet = TmpFile电子表格
类型TmpPicture = TmpFile图片
类型TmpVideo = TmpFile视频

videoPath :: TmpVideo - > FilePath

或者:

  data TmpFile a = TmpFile a FilePath 
派生显示

videoPath :: TmpFile视频 - > FilePath

但显然它们不能编译。什么是正确的方法来做到这一点?一些其他的想法,没有特别吸引人的:




  • 换行格式 TmpFile 反过来,所以值是 Video(TmpFiletest.avi)等。
  • 制作大量不同的数据类型 VideoTmpFile PictureTmpFile

  • 制作 TmpFile typeclass

  • 在任何地方都使用部分函数,​​但添加保护函数以抽象模式匹配


我也考虑学习 -XDataKinds 扩展名,但怀疑我错过了更简单的事情,可以在没有它的情况下完成。



编辑:今天我学习了很多!我尝试了下面列出的两种方法( DataKinds 和幻像类型,它们具有可以用另一个扩展名删除的虚拟值构造函数),并且它们都可以工作!然后我试着进一步。除了常规的 TmpFile a 之外,它们都可以让你创建嵌套类型 TmpFile(ListOf a),这很酷。但我暂时决定使用普通的幻像类型(完整的值构造函数),因为您可以对它们进行模式匹配。例如,我很惊讶这实际上有效:

pre $ data $ Spreadsheet =显示
data Video =视频派生显示
数据ListOf a = ListOf派生显示

数据TmpFile a = TmpFile a FilePath
派生显示

videoPath :: TmpFile Video - > FilePath
videoPath(TmpFile Video p)= p

- 读取一个文件,其中包含a类型的文件名列表
并将它们返回为单独的类型tmpfiles
listFiles :: TmpFile(ListOf a) - > IO [TmpFile a]
listFiles(TmpFile(ListOf fmt)path)= do
txt< - readFile path
let paths = map(TmpFile fmt)(lines txt)
返回路径

vidPath :: TmpFile视频
vidPath = TmpFile视频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 Videovideo1.avi,TmpFile Videovideo2.avi]
return $ map videoPath paths - - [video1.avi,video2.avi]

据我所知,与 DataKinds 相当,但不能访问 fmt 作为值:

  { - #LANGUAGE DataKinds,KindSignatures# - } 

data FileFormat
= Spreadsheet
|图片
|视频
| ListOf FileFormat
派生显示

数据TmpFile(a :: FileFormat)= TmpFile FilePath
派生显示

vidPath :: TmpFile视频
vidPath = TmpFilevideo.avi

vidsList :: TmpFile(ListOf Video)
vidsList = TmpFilevideos.txt
$ b $ 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(行txt)
返回路径

main :: IO [FilePath]
main = do
paths< - listFiles vidsList
return $ map videoPath paths

(这可能看起来像是一件奇怪的事情,但我的实际程序将成为一种小语言的解释器,它编译为具有对应于每个变量,所以tmpfiles的类型列表将会很有用)



这看起来合适吗?我更喜欢 DataKinds 的想法,所以如果我可以将它们视为值,或者如果事实证明这是永远不需要的话,我会选择它。

b $ b

解决方案

你是对的: -XDataKinds TmpFile视频 - > FilePath 方法可行。

  { - #LANGUAGE DataKinds# - } 
这个扩展名可能是一个很好的应用程序。
data TmpFile(a :: FileFormat)= TmpFile FilePath
派生显示

videoPath :: TmpFile视频 - > FilePath

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



当然还有另一种生成类型级别实体的方法: define types 的!

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

data TmpFile a = TmpFile a FilePath
派生显示

videoPath :: TmpFile视频 - > FilePath

这种类型称为幻影类型。但实际上,他们对于以前缺乏适当的类型值的方法有一些破解,DataKinds现在已经给我们提供了。因此,除非您需要与旧编译器兼容,否则请使用DataKinds!



另一种方法是在编译时强制文件类型,但只是简单地说它的功能是部分的。

  data TmpFile = TmpFile FileFormat FilePath 
导出显示

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

实际上,这种方法可能更合理,取决于你打算做什么。


I'm not sure how to word this question. Say I'm trying to pass the paths of tmpfiles around, and I want to capture the idea that there are different formats of tmpfile, and each function only works on one of them. This works:

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!"

But there must be a better way to write it without runtime errors right? I thought of two alternatives, this:

type TmpSpreadsheet = TmpFile Spreadsheet
type TmpPicture     = TmpFile Picture
type TmpVideo       = TmpFile Video

videoPath :: TmpVideo -> FilePath

Or this:

data TmpFile a = TmpFile a FilePath
  deriving Show

videoPath :: TmpFile Video -> FilePath

But obviously they don't compile. What's the proper way to do it? Some other ideas, none particularly appealing:

  • Wrap TmpFile in the format instead of the other way around, so the values are Video (TmpFile "test.avi") etc.
  • Make lots of separate data types VideoTmpFile, PictureTmpFile etc.
  • Make a TmpFile typeclass
  • Use partial functions everywhere, but add guard functions to abstract the pattern matching

I also considered learning the -XDataKinds extension, but suspect I'm missing something much simpler that can be done without it.

EDIT: I'm learning a lot today! I tried both the approaches outlined below (DataKinds and phantom types, which have dummy value constructors that can be removed with another extension), and they both work! Then I tried to go a little further. They both let you make a nested type TmpFile (ListOf a) in addition to regular TmpFile a, which is cool. But I've tentatively decided to go with plain phantom types (intact value constructors), because you can pattern match on them. For example, I was surprised that this actually works:

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"]

As far as I can tell, the equivalent with DataKinds is very similar, but can't access fmt as a value:

{-# 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

(It may seem like a weird thing to want, but my actual program is going to be an interpreter for a small language that compiles to Shake rules with a tmpfile corresponding to each variable, so typed lists of tmpfiles will be useful)

Does that seem right? I like the idea of DataKinds better, so I would go with it instead if I could inspect them as values, or if it turns out that's never needed.

解决方案

You're right: with -XDataKinds, the TmpFile Video -> FilePath approach would work. And indeed I think this may be a good application for that extension.

{-# LANGUAGE DataKinds #-}

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

videoPath :: TmpFile Video -> FilePath

The reason you need this extension to write TmpFile Video is that the constructors of FileFormat are ab initio value-level (thus only exist at runtime), while TmpFile is type-level / compile-time.

Of course there's another way to generate type-level entities: define types!

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

data TmpFile a = TmpFile a FilePath
  deriving Show

videoPath :: TmpFile Video -> FilePath

Such types are called phantom types. But really, they're a bit of a hack to work around the former lack of proper type-level values, which DataKinds has now given us. So, unless you need compatibility with old compilers, do use DataKinds!

An alternative would be to not enforce the file type at compile time, but simply make it explicit that the functions are partial.

data TmpFile = TmpFile FileFormat FilePath
  deriving Show

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

In fact, that approach might well be the more rational one, depending on what you're planning to do.

这篇关于在Haskell中,如何将函数限制为只有一个数据类型的构造函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆