Haskell类型与新类型相关的类型安全性
我知道newtype
与Haskell中的data
相比经常被比较,但我从更多的设计角度而不是技术问题来比较这种比较。
在不确定/ OO语言中,存在反模式“原始痴迷”,其中原始类型的大量使用降低了程序的类型安全性并且引入了相同类型值的意外互换性,否则旨在用于不同目的。 例如,许多事情可以是一个字符串,但如果一个编译器静态地知道我们的意思是我们的名字和我们的意思是在一个地址中成为城市,那将会很好。
那么,如何常话,就哈斯克尔程序员使用newtype
给类型来区分,否则原始值? type
的使用引入了一个别名并赋予程序的可读性更清晰的语义,但不会防止意外地交换值。 当我学习haskell时,我注意到类型系统和我遇到的类型系统一样强大。 因此,我认为这是一个自然而又常见的做法,但我还没有看到太多或使用的任何讨论newtype
在这种光线。
当然,很多程序员都以不同的方式做事,但这在haskell中常见吗?
新类型的主要用途是:
我正在研究一个应用程序,我现在正在广泛使用newtypes。 newtypes
在Haskell是一个纯粹的编译时的概念。 例如下面的解包器, unFilename (Filename "x")
编译为与“x”相同的代码。 运行时间绝对为零。 有data
类型。 这是实现上述目标的一个非常好的方法。
-- | A file name (not a file path).
newtype Filename = Filename { unFilename :: String }
deriving (Show,Eq)
我不想不小心将其视为文件路径。 这不是文件路径。 这是数据库中某个概念文件的名称。
对于算法来说,正确的事物是非常重要的,新类型对此有所帮助。 这对安全性也非常重要,例如,考虑将文件上传到Web应用程序。 我有这些类型:
-- | 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的所有功能都将使用ConferenceId类型。 出现的是一种在编译时被强制执行的功能级功能合同系统。 很不错。 所以是的,我尽可能经常使用它,特别是在大型系统中。
我认为这主要是情况的问题。
考虑路径名。 标准前奏具有“键入FilePath = String”,因为为了方便起见,您想要访问所有字符串和列表操作。 如果你有“newtype FilePath = FilePath String”,那么你需要filePathLength,filePathMap等等,否则你会永远使用转换函数。
另一方面,考虑SQL查询。 SQL注入是一个常见的安全漏洞,所以有这样的东西是有道理的
newtype Query = Query String
然后添加额外的函数,通过转义引号字符将字符串转换为查询(或查询片段),或者以相同的方式填充模板中的空白。 这样,您不会无意中将用户参数转换为查询,而无需通过引用转义函数。
对于简单的X = Y
声明, type
是文档; newtype
类型是类型检查; 这就是为什么newtype
与data
进行比较的原因。
我相当经常使用newtype
来描述你所描述的目的:确保与其他类型存储(并经常被操纵)的东西不会与其他类型混淆。 这样它就可以作为一个稍微高效的data
声明。 没有特别的理由要选择一个。 请注意,使用GHC的GeneralizedNewtypeDeriving
扩展功能,您可以自动导出Num
等类,从而可以像使用Int
或其下的任何谎言一样添加和减少温度或日元。 然而,人们想要对此有点小心。 通常一个不会将温度乘以另一个温度!
对于这些东西的使用频率,在一个相当大的项目我的工作,现在的想法,我有大约122使用的data
,39个应用newtype
,和96种用途type
。
但就“简单”类型而言,该比率比说明的要近一些,因为这96个type
中的32个实际上是函数类型的别名,例如
type PlotDataGen t = PlotSeries t -> [String]
在这里你会注意到两个额外的复杂性:首先,它实际上是一个函数类型,不仅仅是一个简单的X = Y
别名,其次是它的参数化: PlotDataGen
是一个类型构造函数,我应用于另一个类型来创建一个新类型,比如作为PlotDataGen (Int,Double)
。 当你开始做这类事情时, type
不再只是文档,而是实际上是一个函数,尽管在类型级而不是数据级。
偶尔会在type
不能被使用的地方使用newtype
,比如递归类型定义是必须的,但我觉得这很少见。 因此,它的样子,在这个特定的项目至少,我的“原始”的类型定义,约40%是newtype
和60%的type
秒。 几个的newtype
曾经是类型的定义,并明确地转化为你提到的具体原因。
所以,简而言之,是的,这是一个常用的习惯用法。
链接地址: http://www.djcxy.com/p/43271.html