不透明类型是 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类型来拿到宝藏,完全不需要知道密码。
更糟的是,你甚至能用同样的方式拿到密码!
让我们用不透明类型来解决这个问题。
定义一个只有一个变体的 Password 自定义类型。
这个变体应该关联一个 String 值,用来存放密码。
你应该导出 Password 类型,但不要导出它的变体。
这样Password就成了不透明类型,意味着你再也无法通过解构来获取密码。
在生产代码中无法检验一个类型是否是不透明类型,你可以使用 elm-review(一个静态分析工具)来强制保证这一点。
定义并导出createPassword函数,并写上类型定义,它接收一个String,返回一个Maybe Password。
OWASP 建议密码至少要有 8 个字符,所以我们用 Parse, don't validate 模式来保证这个不变量。
如果密码不足 8 个字符,createPassword应返回Nothing,否则应返回Just Password。
你应该导出SecureTreasureChest类型,但不要导出它的变体。
这样SecureTreasureChest就成了不透明类型,意味着你再也无法通过解构来拿到密码或宝藏。
这个函数应该接收两个形参,并且要有类型定义。
Password(用来存放密码)这个函数应返回一个SecureTreasureChest。
导出这个函数,这样其他模块就能创建SecureTreasureChest的实例。
这个函数应该接收两个形参,并且要有类型定义。
String(用来表示密码)SecureTreasureChest泛型自定义类型这个函数要用提供的密码去比对SecureTreasureChest中的Password。
这个函数应返回一个Maybe。
如果密码匹配,就返回Just,其中带有SecureTreasureChest里的泛型值(也就是宝藏!)。
如果密码不匹配,就返回Nothing。
导出这个函数,这样其他模块只要提供正确的密码,就能从SecureTreasureChest中取出宝藏。