使用新类型改善类型安全性
别名类型减少了样板并提高了可读性,但它不比别名类型本身更安全。考虑以下:
type alias Email = String
type alias Name = String
someEmail = "holmes@private.com"
someName = "Benedict"
sendEmail : Email -> Cmd msg
sendEmail email = ...
使用上面的代码,我们可以编写 sendEmail someName
,它会编译,即使它真的不应该,因为尽管名称和电子邮件都是 String
s,但它们完全不同。
通过创建一个新**类型,**我们可以在类型级别上真正区分一个 String
和另一个 String
。这是一个将 Email
重写为 type
而不是 type alias
的示例:
module Email exposing (Email, create, send)
type Email = EmailAddress String
isValid : String -> Bool
isValid email =
-- ...validation logic
create : String -> Maybe Email
create email =
if isValid email then
Just (EmailAddress email)
else
Nothing
send : Email -> Cmd msg
send (EmailAddress email) = ...
我们的 isValid
函数可以确定字符串是否是有效的电子邮件地址。create
函数检查给定的 String
是否是有效的电子邮件,返回 Maybe
-wrapped Email
以确保我们只返回有效的地址。虽然我们可以通过编写 EmailAddress "somestring"
直接构造 Email
来回避验证检查,如果我们的模块声明没有暴露 EmailAddress
构造函数,如此处所示
module Email exposing (Email, create, send)
那么没有其他模块可以访问 EmailAddress
构造函数,尽管他们仍然可以在注释中使用 Email
类型。在此模块之外构建新 Email
的唯一方法是使用它提供的 create
函数,该函数确保它首先只返回有效的电子邮件地址。因此,此 API 通过其类型安全性自动引导用户沿着正确的路径:send
仅适用于由执行验证的 create
构造的值,并且因为它返回 Maybe Email
而强制处理无效的电子邮件。
如果你想导出 Email
构造函数,你可以写
module Email exposing (Email(EmailAddress), create, send)
现在任何导入 Email
的文件也可以导入其构造函数。在这种情况下,这样做将允许用户回避验证和无效的电子邮件,但你并不总是构建这样的 API,因此导出构造函数可能很有用。对于具有多个构造函数的类型,你可能还只想导出其中的一些构造函数。