Haskelltypes与newtypetypes安全性有关

我知道newtype与Haskell中的data相比经常被比较,但是我从更多的deviseangular度来比较这个比较,而不是技术问题。

在不确定/ OO语言中,存在反模式的“ 原始痴迷 ”,其中原始types的大量使用降低了程序的types安全性并且引入了相同types值的意外互换性,否则旨在用于不同目的。 例如,很多东西可以是一个string,但是如果一个编译器能够静态地知道我们的意思是一个名字,我们的意思是在一个地址中的城市,这将是很好的。

那么,Haskell程序员多newtype使用newtype来区别原始值呢? type的使用引入了一个别名,并赋予程序的可读性更清晰的语义,但并不防止意外地交换值。 当我学习haskell时,我注意到types系统和我所遇到的一样强大。 因此,我认为这是一个自然而常见的做法,但从这个angular度来看,我还没有看到太多或者有什么新用法的讨论。

当然,很多程序员都是以不同的方式做事,但这在haskell中是如此普遍吗?

新型的主要用途是:

  1. 用于定义types的替代实例。
  2. 文档。
  3. 数据/格式正确性保证。

我现在正在进行一个应用程序的广泛使用newtypes。 Haskell中的新types是纯粹的编译时间概念。 例如下面的解包程序, unFilename (Filename "x")编译为与“x”相同的代码。 运行时间绝对为零。 有datatypes。 这是实现上述目标的一个非常好的方法。

 -- | A file name (not a file path). newtype Filename = Filename { unFilename :: String } deriving (Show,Eq) 

我不想不小心把这个当成文件path。 这不是一个文件path。 这是数据库中某个概念文件的名称。

对于algorithm来说正确的事情是非常重要的,newtypes帮助这个。 这对于安全性也非常重要,例如,考虑将file upload到Web应用程序。 我有这些types:

 -- | A sanitized (safe) filename. newtype SanitizedFilename = SanitizedFilename { unSafe :: String } deriving Show -- | Unique, sanitized filename. newtype UniqueFilename = UniqueFilename { unUnique :: SanitizedFilename } deriving Show -- | An uploaded file. data File = File { file_name :: String -- ^ Uploaded file. ,file_location :: UniqueFilename -- ^ Saved location. ,file_type :: String -- ^ File type. } deriving (Show) 

假设我有这个函数清理已经上传的文件的文件名:

 -- | Sanitize a filename for saving to upload directory. sanitizeFilename :: String -- ^ Arbitrary filename. -> SanitizedFilename -- ^ Sanitized filename. sanitizeFilename = SanitizedFilename . filter ok where ok c = isDigit c || isLetter c || elem c "-_." 

现在,我生成一个唯一的文件名:

 -- | Generate a unique filename. uniqueFilename :: SanitizedFilename -- ^ Sanitized filename. -> IO UniqueFilename -- ^ Unique filename. 

从任意的文件名生成一个唯一的文件名是很危险的,首先应该对它进行消毒。 同样,一个唯一的文件名因此总是安全的扩展名。 我现在可以将文件保存到磁盘,并将该文件名放在我的数据库中,如果我想。

但是也可能很麻烦,不得不打包/打开很多东西。 从长远来看,我认为这是值得的,尤其是为了避免价值错配。 ViewPatterns有些帮助:

 -- | Get the form fields for a form. formFields :: ConferenceId -> Controller [Field] formFields (unConferenceId -> cid) = getFields where ... code using cid .. 

也许你会说,解开它在一个函数是一个问题 – 如果你错误地将cid传递给一个函数呢? 不是一个问题,使用会议ID的所有function都将使用ConferenceIdtypes。 所出现的是一种在编译时被强制执行的函数级函数级契约系统。 很不错。 所以是,我经常使用它,特别是在大系统中。

我认为这主要是情况的问题。

考虑path名。 标准前奏有“typesFilePath = String”,因为,为了方便起见,你想要访问所有的string和列表操作。 如果你有“newtype FilePath = FilePath String”那么你需要filePathLength,filePathMap等等,否则你会永远使用转换函数。

另一方面,考虑SQL查询。 SQL注入是一个常见的安全漏洞,所以有这样的东西是有道理的

 newtype Query = Query String 

然后添加额外的函数,通过转义引号将string转换为查询(或查询片段),或者以相同的方式填充模板中的空格。 这样你就不会意外地将用户参数转换成查询,而不需要通过引号转义函数。

对于简单的X = Y声明, type是文档; newtypetypes是types检查; 这就是为什么newtypedata进行比较的原因。

我相当频繁地使用newtype只是为了描述你的目的:确保存储(并经常被操纵)的东西与其他types相同的方式不会与其他types混淆。 这样它就可以作为一个稍微高效的data声明。 没有什么特别的理由要select一个。 请注意,使用GHC的GeneralizedNewtypeDeriving扩展,您可以自动派生Num等类,让您的温度或日元被加减,就像使用Int或其下的任何谎言一样。 但是,一个人想要小心一点, 通常情况下,一个温度乘以另一个温度!

对于这些东西被多久使用的想法,在我现在正在处理的一个相当大的项目中,我有大约122个data使用,39个使用新type ,96个使用type

但就“简单”types而言,这个比率比说明的要近一些,因为这96个type中的32个实际上是函数types的别名,比如

 type PlotDataGen t = PlotSeries t -> [String] 

在这里你会注意到两个额外的复杂性:首先,它实际上是一个函数types,不仅仅是一个简单的X = Y别名,其次是它的参数化: PlotDataGen是一个types构造函数,我应用于另一个types来创build一个新types,比如作为PlotDataGen (Int,Double) 。 当你开始做这种事时, type不再只是文档,而是实际上是一个函数,尽pipe在types级而不是数据级。

newtype偶尔会在type不能被使用的地方使用,比如recursiontypes定义是必须的,但是我觉得这是相当less见的。 因此,至less在这个特定的项目中,我的“原始”types定义中有40%是新types,60%是type s。 几个新的types定义曾经是types,并确切地转换为你提到的确切原因。

所以,简而言之,是的,这是一个常用的习惯用语。

我认为使用新types的区别是相当普遍的。 在许多情况下,这是因为您想要给不同types的类实例,或隐藏实现,但只是想防止意外转换,也是一个明显的原因。