Eswlnk Blog Eswlnk Blog
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈
  • 注册
  • 登录
首页 › 代码发布 › 小伍关于Elixir中的代数数据类型的详细解答

小伍关于Elixir中的代数数据类型的详细解答

Eswlnk的头像
Eswlnk
2022-06-01 21:43:10
小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog
智能摘要 AI
Elixir是一种动态类型语言,运行时检查类型并使用Dialyzer工具进行类型规范验证。Dialyzer通过静态分析帮助开发者减少错误,而不像其他静态类型语言需严格证明代码正确性。Elixir支持代数数据类型(ADT),包括乘积类型(AND)和和类型(OR),用于构建复杂的数据模型,限制无效状态,提高代码可靠性。通过将状态和字段结合为单一的sum类型,可以有效避免非法状态,增强代码的正确性和可维护性。使用Dialyzer和ADT不仅有助于发现错误,还能提供额外的代码信息,便于理解和重构。尽管ADT并非灵丹妙药,但对于使用Dialyzer的Elixir项目而言,是一个强大的辅助工具。

Elixir是一种动态类型语言。Elixir中的类型在程序运行时检查,而不是在编译时检查。如果它们不匹配,则会引发异常。在静态类型语言中,类型是在编译时检查的。这可以帮助我们编写正确、可理解和可重构的代码。但它也引入了对类型的某种关注,将其作为应用程序的基础。一个有趣的概念是使用类型来为您的业务领域建模。在 Haskell、F# 和 OCaml 等语言中,这通常是使用代数数据类型 (ADT) 完成的——它们通过将类型与乘积 (AND) 和总和 (OR) 类型聚合来构建复合数据类型。在静态分析工具 Dialyzer 的帮助下,您可以使用 ADT 来限制应用程序允许状态的数量。这减少了错误溜进来的机会。

小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog

在 Elixir 中使用 Dialyzer 进行类型声明

在 Elixir(和其他 BEAM 语言)中,检查类型规范通常使用Dialyzer完成。Dialyzer 不同于 Haskell、OCaml 甚至 TypeScript 的类型系统。Dialyzer 需要向您证明您的代码不正确,而不是您向编译器证明您的代码是正确的。

这使得 Dialyzer 的要求相当宽松。如果类型有办法工作,Dialyzer 会假设您知道自己在做什么,并且类型确实可以工作。但是推理类型和捕捉偶尔出现的错误仍然很有用。

Elixir中透析器的快速介绍

你可以在 Elixir 中使用 Dialyzer,通过为你的函数添加类型规范@spec。

小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog
#          (1)      (2)           (3)
  @spec plus_one(integer) :: integer
 
  def plus_one(x), do: x + 1
end

在这里,我们只写函数的名称 (1) 及其类型作为参数 (2),然后是函数的返回类型 (3)。

您可以在 Elixir 的文档中找到要使用的基本类型列表。

您还可以通过创建自己的类型别名@type。为此,您需要提供别名的名称 (1) 及其类型 (2)。

#         (1)        (2)
  @type counter :: integer

如果您使用 Elixir 语言服务器,这就是您需要做的所有事情:您的插件/扩展将通知您 Dialyzer 不喜欢的任何规范。否则,您需要运行mix dialyzer任务来检查您的类型。

你可以在 Elixir 的网站和Elixir 的 typespec 文档中找到更多关于Dialyzer 的使用。

现在我们可以指定我们的 Elixir 代码,让我们深入研究代数数据类型。

代数数据类型

小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog

虽然这个名字听起来很吓人(哦,代数?),但代数数据类型相对简单。

本节将重点介绍 ADT 的两个主要部分:乘积 (AND) 和和 (OR) 类型。

产品类型

产品类型无处不在。产品类型只是具有两个或多个字段的类型,每个字段都包含一个数据类型。您也可以将它们视为 AND 类型。

例如,元组是一种产品类型:

@type tuple(a, b) :: {a, b}

它采用两种数据类型——a和b——并返回一个包含这两种数据类型的类型。

在 Elixir 中,我们还使用带有命名字段的产品类型——结构。

defmodule Person do
  defstruct first_name: "Gints", last_name: "Dreimanis"
end

让我们看看这个例子中的类型:

@type t() :: %__MODULE__{
        first_name: String.t(),
        last_name: String.t()
      }

通常,您可以将类型视为可能值的集合。例如,布尔值有两个可能的值::true和:false。交通灯颜色的类型具有以下三个可能值之一::green、:yellow和:red。

如果我们有两个 sizea和b的类型,那么这些类型的 product 类型将包含a * b值——这些类型的 size 的乘积。如果您制作布尔值和交通灯类型的产品类型——例如,将布尔值和交通灯放在一个元组中——您将有2 * 3 = 6可能的值。

{:green, :true}
{:yellow, :true}
{:red, :true}
{:green, :false}
{:yellow, :false}
{:red, :false}
小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog

总和类型

历史上不太常见的类型是 sum 类型。

与产品类型相比,总和类型为您提供两个(或更多)选项之一。您也可以将它们视为 OR 类型。

我们也在 Elixir 中使用这些。例如,结果元组是求和类型。

@type result(a, b) :: {:error, a} | {:ok, b}

在结果元组中,我们可能会遇到类型错误a 或类型成功b。

或者,例如,我们可以为可选值设置一个 sum 类型。

@type optional(a) :: :error | {:ok, a}

但它也可以仅用于制作替代品列表。

@type direction :: :north | :west | :south | :east

如果您在 sum 类型中列出两种类型,则生成的类型可以从一组值或另一组值中选择一个类型。因此,它的大小通常是这些类型的大小之和。

在使用 Dialyzer 时,上述情况可能并不总是正确的:您可以将两个重叠的集合放在一起。为了使陈述成立,它们需要用它们来自的集合进行标记——我们上面定义的结果类型就是一个很好的例子。

这就是人们通常所说的代数数据类型。

代数数据类型还有更多内容

通过将总和和乘积放在一起,我们就有了类似于我们在学生时代就知道和喜爱的代数:乘法、总和和变量。

当然,代数类型不仅限于此:还有递归、指数等。如果您想更深入地研究该主题,请查看它们在 Haskell 等语言中的外观。

小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog

代数数据类型如何帮助领域建模

我们 Elixir 程序员通常不会考虑求和类型。在 Elixir 中对域建模的主要工具是 struct,这是一种具有命名字段的产品类型。

虽然这对大多数事情来说已经足够了,但有时使用 sum 类型也是有益的。

让我们看一个例子。

自定义看板

假设我们需要创建一个自定义看板问题的表示。

我们的问题可能处于以下状态之一:

  • 搜索受让人:在这种情况下,它应该既没有受让人也没有审阅者
  • 尚未开始:在此和以下状态下,它应该有一个受理人但没有审阅者
  • 进行中
  • In review:在这个和下面的状态下,应该有一个assignee和一个reviewer
  • 完毕

所有问题也都有名称和描述。

一开始,人们可能会想使用一个简单的结构。

defmodule Issue do
  defstruct name: "",
            description: "",
            state: :searching_for_assignee
            assignee: nil,
            reviewer: nil
end

但是正如我们在产品类型部分看到的,一个简单的产品类型有很多可能的值,其中一些可能不符合我们的要求。

例如,我们可以创建一个搜索受让人但仍有受让人和审阅者的问题。

iex(1)> %Issue{name: "wrong issue", description: "not good at all", state: :searching_for_assignee, assignee: "Jorge Luis Borges", reviewer: "Gabriel García Márquez"}
%Issue{
  assignee: "Jorge Luis Borges",
  description: "not good at all",
  name: "wrong issue",
  reviewer: "Gabriel García Márquez",
  state: :searching_for_assignee
}

虽然通常可以避免这样做,但让它变得不可能更简单。我们对此有句俗语:“使非法国家无法代表”。

为此,我们需要创建一个 sum 类型,涵盖我们想要允许的所有状态。它将使我们能够通过用值的总和代替值的乘积来消除一些错误的状态。

首先,让我们将state、assignee和reviewer字段合并为一个字段:state。

defstruct name: "",
          description: "",
          state: :searching_for_assignee

之后,让我们定义一个 sum 类型state,它将包含我们指定的选项。

让我们再看看他们。我们的问题可能处于以下状态之一:

  • 寻找受让人
  • 尚未开始,但有受让人
  • 进行中并与受让人
  • 与受让人和审阅者一起审阅
  • 完成,受让人和审阅者留作历史记录

定义一个几乎像这样读取的类型非常容易:

@type state ::
        :searching_for_assignee
        | {:not_started, String.t()}
        | {:in_progress, String.t()}
        | {:in_review, String.t(), String.t()}
        | {:done, String.t(), String.t()}

为了更容易理解,我们可以为受理人和审阅者创建别名。

@type assignee :: String.t()
@type reviewer :: String.t()

现在,该类型看起来与我们的规则列表完全一样。

@type state ::
        :searching_for_assignee
        | {:not_started, assignee}
        | {:in_progress, assignee}
        | {:in_review, assignee, reviewer}
        | {:done, assignee, reviewer}

剩下的就是Issue使用我们的状态类型为模块 struct () 创建一个类型规范。

@type t() :: %__MODULE__{
        name: String.t(),
        description: String.t(),
        state: state
      }

这是完整的模块代码:

defmodule Issue do
  defstruct name: "",
            description: "",
            state: :searching_for_assignee
 
  @type assignee :: String.t()
  @type reviewer :: String.t()
  @type state ::
          :searching_for_assignee
          | {:not_started, assignee}
          | {:in_progress, assignee}
          | {:in_review, assignee, reviewer}
          | {:done, assignee, reviewer}
 
  @type t() :: %__MODULE__{
          name: String.t(),
          description: String.t(),
          state: state
        }
end

现在我们可以测试这种类型规范是否可以阻止我们犯逻辑错误。

我们将创建一个为问题添加审阅者的函数,但我们会在其中添加一个错误:它不会改变问题的状态。我们还将添加一个类型规范。

@spec add_assignee(Issue.t(), assignee) :: Issue.t()
def add_assignee(%{state: :searching_for_assignee} = issue, assignee_name) do
  %{issue | state: {:searching_for_assignee, assignee_name}}
end

Dialyzer 将在此处正确返回类型错误:

lib/issue.ex:21:invalid_contract
The @spec for the function does not match the success typing of the function.
 
Function:
Issue.add_assignee/2
 
Success typing:
@spec add_assignee(%{:state => :searching_for_assignee, _ => _}, _) :: %{
  :state => {:searching_for_assignee, _},
  _ => _
}

这有点神秘,但它基本上意味着Issue.add_assignee没有编译,我们应该调查一下!?

如您所见,代数数据类型使我们免于犯错。事实证明,他们并不是真正的可怕怪物,而是朋友。

Elixir应用程序的代数数据类型的好处

为 Elixir 应用程序采用代数数据类型是一个两步决策过程。

第一步是选择使用 Dialyzer 和 typespecs。Dialyzer提供了任何具有静态类型的语言的大部分好处:

  • 更容易发现你在代码中犯的错误。
  • 类型提供了关于代码的额外信息:它做什么以及它操作什么值。这在尝试理解代码时很有帮助。
  • 编写代码后,类型可以确保代码仍然执行相同的操作(类似于测试),因此更容易重构。
小伍关于Elixir中的代数数据类型的详细解答-Eswlnk Blog

一旦您将 Dialyzer 用于您的代码库,就应该自然而然地考虑代数数据类型,并带来一些好处:

  • 正如我们在示例中看到的那样,sum 类型尤其可以让您减少可能的状态并使非法状态无法表示。
  • 在你的词汇表中使用 AND 和 OR 可以帮助你以一种即使对于非开发人员(领域专家)也很直观和易于理解的方式构建复合类型。

当然,ADT 只是软件正确性的一种工具——绝对不是灵丹妙药。但总的来说,ADT 对任何使用使用 Dialyzer 的 Elixir 代码库的人来说都是一个有用的概念。

如果您的代码库不使用 Dialyzer,那么您的首要目标应该是引入它,这比在编写类型规范时更改类型的方式要大得多。不幸的是,这项工作超出了本文的范围。

本站默认网盘访问密码:1166
本站默认网盘访问密码:1166
ElixirElixir效率Erlang现代编程编程语言
0
0
Eswlnk的头像
Eswlnk
一个有点倒霉的研究牲站长
赞赏
巧妙应用Docker运行HuggingFace
上一篇
如何通过使用CloudFlare的转换规则实现隐藏bucket路径
下一篇

评论 (0)

请登录以参与评论
现在登录
    发表评论

猜你喜欢

  • 「日志记录」逆向必应翻译网页版API实现免费调用
  • 「代码分享」第三方平台VIP视频解析API接口
  • 「至臻原创」某系统网站登录功能监测
  • 「开发日志」在Vue3中如何为路由Query参数标注类型
  • 「其他分享」分享一个在Tun模式下可用的脚本
Eswlnk的头像

Eswlnk

一个有点倒霉的研究牲站长
1108
文章
319
评论
679
获赞

随便看看

wordpress自定义图标美化
2021-06-14 19:30:32
「至臻原创」某系统网站登录功能监测
2024-12-28 21:43:38
Nestjs配置全局路由前缀实现接口区分
2024-01-08 12:30:21

文章目录

专题展示

WordPress53

工程实践37

热门标签

360 AI API CDN java linux Nginx PDF PHP python SEO Windows WordPress 云服务器 云服务器知识 代码 免费 安全 安卓 工具 开发日志 微信 微软 手机 插件 攻防 攻防对抗 教程 日志 渗透分析 源码 漏洞 电脑 破解 系统 编程 网站优化 网络 网络安全 脚本 苹果 谷歌 软件 运维 逆向
  • 首页
  • 知识库
  • 地图
Copyright © 2023-2025 Eswlnk Blog. Designed by XiaoWu.
本站CDN由 壹盾安全 提供高防CDN安全防护服务
蜀ICP备20002650号-10
页面生成用时 0.975 秒   |  SQL查询 46 次
本站勉强运行:
友情链接: Eswlnk Blog 网站渗透 倦意博客 特资啦!个人资源分享站 祭夜博客 iBAAO壹宝头条
  • WordPress142
  • 网络安全64
  • 漏洞52
  • 软件52
  • 安全48
现在登录
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈