使用新类型改善类型安全性

别名类型减少了样板并提高了可读性,但它不比别名类型本身更安全。考虑以下:

type alias Email = String

type alias Name = String

someEmail = "holmes@private.com"

someName = "Benedict"

sendEmail : Email -> Cmd msg
sendEmail email = ...

使用上面的代码,我们可以编写 sendEmail someName,它会编译,即使它真的不应该,因为尽管名称和电子邮件都是 Strings,但它们完全不同。

通过创建一个新**类型,**我们可以在类型级别上真正区分一个 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,因此导出构造函数可能很有用。对于具有多个构造函数的类型,你可能还只想导出其中的一些构造函数。