Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Haskell中,如何将函数限制为一个数据类型的一个构造函数?_Haskell_Types_Type Systems - Fatal编程技术网

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

在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

我不知道这个问题该怎么说。假设我正在尝试传递tmpfile的路径,我想捕捉一个想法,即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
    typeclass
  • 到处使用分部函数,但添加保护函数来抽象模式匹配
我也考虑过学习
-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