Haskelltypes与newtypetypes安全性有关
我知道newtype
与Haskell中的data
相比经常被比较,但是我从更多的deviseangular度来比较这个比较,而不是技术问题。
在不确定/ OO语言中,存在反模式的“ 原始痴迷 ”,其中原始types的大量使用降低了程序的types安全性并且引入了相同types值的意外互换性,否则旨在用于不同目的。 例如,很多东西可以是一个string,但是如果一个编译器能够静态地知道我们的意思是一个名字,我们的意思是在一个地址中的城市,这将是很好的。
那么,Haskell程序员多newtype
使用newtype
来区别原始值呢? type
的使用引入了一个别名,并赋予程序的可读性更清晰的语义,但并不防止意外地交换值。 当我学习haskell时,我注意到types系统和我所遇到的一样强大。 因此,我认为这是一个自然而常见的做法,但从这个angular度来看,我还没有看到太多或者有什么新用法的讨论。
当然,很多程序员都是以不同的方式做事,但这在haskell中是如此普遍吗?
新型的主要用途是:
- 用于定义types的替代实例。
- 文档。
- 数据/格式正确性保证。
我现在正在进行一个应用程序的广泛使用newtypes。 Haskell中的新types是纯粹的编译时间概念。 例如下面的解包程序, unFilename (Filename "x")
编译为与“x”相同的代码。 运行时间绝对为零。 有data
types。 这是实现上述目标的一个非常好的方法。
-- | 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
是文档; newtype
types是types检查; 这就是为什么newtype
与data
进行比较的原因。
我相当频繁地使用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的类实例,或隐藏实现,但只是想防止意外转换,也是一个明显的原因。