首页 文章

关于类型安全的Haskell类型与newtype [关闭]

提问于
浏览
62

我知道 newtype 经常被比作Haskell中的 data ,但我从更多的设计观点而非技术问题中提出这种比较 .

在不完全/ OO语言中,存在反模式“primitive obsession”,其中原始类型的大量使用降低了程序的类型安全性并且引入了相同类型值的意外互换性,否则用于不同目的 . 例如,很多东西都可以是String,但如果编译器可以静态地知道我们的名字是什么以及我们想要成为地址中的城市,那将会很好 .

那么,Haskell程序员多久使用 newtype 给其他原始值赋予类型区别呢? type 的使用引入了一个别名,并提供了一个程序's readability clearer semantics, but doesn' t,以防止意外地交换值 . 当我学习haskell时,我注意到类型系统和我遇到的任何类型系统一样强大 . 因此,我认为这是一种自然而普遍的做法,但我没有看到太多或任何有关 newtype 在这方面的使用的讨论 .

当然,很多程序员都会以不同的方式做事,但这在haskell中是否常见?

4 回答

  • 16

    newtypes的主要用途是:

    • 用于定义类型的替代实例 .

    • 文档 .

    • 数据/格式正确性保证 .

    我正在开发一个应用程序,我现在广泛使用newtypes . Haskell中的 newtypes 是一个纯粹的编译时概念 . 例如 . 使用下面的unwrappers, unFilename (Filename "x") 编译为与"x"相同的代码 . 运行时间绝对是零 . 有 data 类型 . 这使它成为实现上述目标的一种非常好的方式 .

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

    我不想意外地将其视为文件路径 . 它不是文件路径 . 它是数据库中某个概念文件的名称 .

    算法引用正确的东西非常重要,newtypes有助于此 . 这对于安全性也非常重要,例如,考虑将文件上传到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.
    

    从任意文件名生成唯一文件名是危险的,应首先对其进行清理 . 同样,唯一的文件名因此通过扩展始终是安全的 . 我现在可以将文件保存到磁盘,如果我愿意,可以将该文件名放在我的数据库中 .

    但是必须包装/打开很多东西也很烦人 . 从长远来看,我认为特别值得避免 Value 不匹配 . ViewPatterns有所帮助:

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

    也许你会说在一个函数中展开它是一个问题 - 如果你错误地将 cid 传递给函数会怎么样?不是问题,使用 Session ID的所有功能都将使用ConferenceId类型 . 出现的是一种在编译时被强制使用的功能到功能级别的 Contract 系统 . 挺棒的 . 所以是的,我尽可能经常使用它,特别是在大系统中 .

  • 19

    我认为这主要是情况问题 .

    考虑路径名 . 标准前奏有“type FilePath = String”,因为为方便起见,您希望能够访问所有字符串和列表操作 . 如果您有“newtype FilePath = FilePath String”,那么您将需要filePathLength,filePathMap等,否则您将永远使用转换函数 .

    另一方面,考虑SQL查询 . SQL注入是一个常见的安全漏洞,因此有类似的东西是有意义的

    newtype Query = Query String
    

    然后添加额外的函数,通过转义引号字符将字符串转换为查询(或查询片段),或以相同的方式填充模板中的空格 . 这样,您就不会在不经过引用转义函数的情况下意外地将用户参数转换为查询 .

  • 58

    对于简单的 X = Y 声明, type 是文档; newtype 是类型检查;这就是 newtypedata 进行比较的原因 .

    我经常使用 newtype 只是为了你描述的目的:确保以与其他类型相同的方式存储(并且经常被操纵)的东西不会与其他东西混淆 . 这样,它就像一个稍微高效的 data 声明一样工作;有's no particular reason to chose one over the other. Note that with GHC' s GeneralizedNewtypeDeriving 扩展名,您可以自动派生类别 Num ,允许您的温度或者可以使用 Int 或其下面的任何内容来添加和减去日元 . 但是,人们希望对此有点小心;通常一个人不会将温度乘以另一个温度!

    为了了解这些东西的使用频率,在我正在进行的一个相当大的项目中,我有大约122个 data 的使用,39个 newtype 的使用,以及 type 的96个用途 .

    但就这个问题而言,这个比例比这个更接近,因为 type 的96个使用中的32个实际上是函数类型的别名,例如

    type PlotDataGen t = PlotSeries t -> [String]
    

    你'll note two extra complexities here: first, it'实际上是一个函数类型,而不仅仅是一个简单的 X = Y 别名,其次是它的参数化: PlotDataGen 是一个类型构造函数,我应用于另一个类型来创建一个新类型,例如 PlotDataGen (Int,Double) . 当你开始做这种事情时, type 不再仅仅是文档,而是实际上是一个函数,虽然在类型级别而不是数据级别 .

    newtype 偶尔会在 type 不能使用的地方使用,例如需要递归类型定义的地方,但我发现这是相当罕见的 . 所以看起来,至少在这个特定的项目中,我的"primitive"类型定义中有大约40%是 newtype ,而60%是 type . 一些 newtype 定义曾经是类型,并且由于您提到的确切原因而被明确转换 .

    所以简而言之,是的,这是一个常见的习语 .

  • 10

    我认为使用 newtype 进行类型区分是很常见的 . 在许多情况下,这是因为您想要提供不同类型的类实例,或隐藏实现,但只是想要防止意外转换也是一个明显的理由 .

相关问题