轨道
/
Elm
Elm
/
练习
/
安全宝箱
安全宝箱

安全宝箱

学习练习

简介

不透明类型

简介

不透明类型是 Elm 中的一种封装技术。 有些人也称之为信息隐藏,或隐藏实现细节。 它们也常用于强制进行验证,使用解析,而非验证模式。 例如,你可以有一个EmailAddress不透明类型,它只能包含有效的电子邮件地址(而不是String,后者可以包含任何内容)。

创建不透明类型

自定义类型有一个或多个变体,编译器会自动生成一个函数来创建每个变体,使用变体名称。 这些函数是创建自定义类型的唯一方式。 编译器也允许对这些变体进行解构。

type Maybe a
    = Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
    | Just a  --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'

默认情况下,所有这些都只存在于文件/模块内部,我们使用exposing关键字来选择导出哪些内容。 要创建不透明类型,只需选择不导出变体。

这样做时,你几乎总是需要通过公开自定义函数,来提供另一种创建和获取变体的方式。 这些自定义函数允许你在创建和获取变体时应用一些逻辑。 这会隐藏模块的内部实现细节,也意味着只要公开的函数保持不变,你可以随意更改实现细节。

解析,而非验证

不透明类型最常见的用途可能就是强制进行验证,使用解析,而非验证模式。 在这种模式中,你公开一个函数,它接收一个输入,而该输入的结构或类型安全性比你想要的更少,例如String,然后把它解析成结构更丰富的类型,例如EmailAddress,要么返回该类型,要么返回一个错误(通常是Maybe或Result)。

例如,在下面的代码中,外部模块只能使用parseEmailAddress函数创建EmailAddress类型,所以当你处理EmailAddress类型时,可以确定它是有效的。

module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )

-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress = 
  EmailAddress String

-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =   
  if isValidEmailAddress candidateEmailAddress then
    Just (EmailAddress candidateEmailAddress)
  else
    Nothing

-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress  emailAdress ) =
  emailAdress

isValidEmailAddress: String -> bool
-- ... 

说明

在这个练习中,你要编写一个泛型的(或者说是充满魔法的!)SecureTreasureChest,用来存放一些宝藏。 眼尖的同学可能已经发现,之前的TreasureChest其实没那么安全! 如果你使用getTreasure函数,就必须提供密码,但你仍然可以直接解构TreasureChest类型来拿到宝藏,完全不需要知道密码。 更糟的是,你甚至能用同样的方式拿到密码! 让我们用不透明类型来解决这个问题。

1. 按照“Parse, don't validate”模式定义 Password 不透明类型

定义一个只有一个变体的 Password 自定义类型。

这个变体应该关联一个 String 值,用来存放密码。

你应该导出 Password 类型,但不要导出它的变体。 这样Password就成了不透明类型,意味着你再也无法通过解构来获取密码。 在生产代码中无法检验一个类型是否是不透明类型,你可以使用 elm-review(一个静态分析工具)来强制保证这一点。

定义并导出createPassword函数,并写上类型定义,它接收一个String,返回一个Maybe Password。

OWASP 建议密码至少要有 8 个字符,所以我们用 Parse, don't validate 模式来保证这个不变量。

如果密码不足 8 个字符,createPassword应返回Nothing,否则应返回Just Password。

2. 导出 SecureTreasureChest,但不要导出它的变体

你应该导出SecureTreasureChest类型,但不要导出它的变体。 这样SecureTreasureChest就成了不透明类型,意味着你再也无法通过解构来拿到密码或宝藏。

3. 定义 createTreasure 函数

这个函数应该接收两个形参,并且要有类型定义。

  • 一个泛型类型(用来存放宝藏)
  • 一个Password(用来存放密码)

这个函数应返回一个SecureTreasureChest。

导出这个函数,这样其他模块就能创建SecureTreasureChest的实例。

4. 定义 getTreasure 函数

这个函数应该接收两个形参,并且要有类型定义。

  • 一个String(用来表示密码)
  • 一个SecureTreasureChest泛型自定义类型

这个函数要用提供的密码去比对SecureTreasureChest中的Password。

这个函数应返回一个Maybe。

如果密码匹配,就返回Just,其中带有SecureTreasureChest里的泛型值(也就是宝藏!)。

如果密码不匹配,就返回Nothing。

导出这个函数,这样其他模块只要提供正确的密码,就能从SecureTreasureChest中取出宝藏。

通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Elm Exercism

准备好开始 安全宝箱 了吗?

注册 Exercism,借助 28 个概念110 个练习 和真人导师指导,学习并掌握 Elm,全部免费。