【PL理论】(5) F#:递归类型 | Immutability 特性(F#中值一旦定义就不会改变)

news2024/11/18 11:23:45

  

  • 💭 写在前面:本文旨在探讨不可变数据结构在 F# 编程中的应用,特别是如何利用递归记录类型来表示和操作数值表达式。通过定义存储整数的二叉树和数值表达式的类型,我们将展示不可变性如何简化程序的理解和维护。文章将对比 F# 与命令式编程语言(如 C 和 C++)在处理类似问题时的差异,强调 F# 不可变性的优势,并通过示例展示如何在 F# 中对表达式求值

目录

0x00 递归类型(Recursive Type)

0x01 关于 “值一旦定义就不会改变” 特性

0x01 举例:语法树


0x00 递归类型(Recursive Type)

让我们定义一个存储整数的(完整的)二叉树,归纳类型定义如下:

  • 基本情况 (Base case):单个叶子是一个二叉树
  • 归纳情况 (Inductive case):一个拥有两个子树的节点也是一棵树
type Tree = Leaf of int | Node of int * Tree * Tree
let t1: Tree = Node (3, Leaf 1, Leaf 2)
let t2: Tree = Node (5, Leaf 4, t1)

注意不可变性!在前面的例子中,如果我们将 t1 更新为一个新值,会发生什么?这会影响 t2 吗?

这是我们在命令式编程语言中所期望的,我们必须始终考虑内存状态和指针。

let t1 = Node (3, Leaf 1, Leaf 2)
let t2 = Node (5, Leaf 4, t1)
let t1 = Leaf 7   // 会发生What?t2会不会变?

在 F# 中,一旦定义了值,它们就不会改变!(就像在数学中一样)

实际上,你并没有更新 t1,你正在定义一个新值(Leaf 7)并给它一个名称 t1。因此,我们不必关心内存状态的 "副作用":

let t1 = Node (3, Leaf 1, Leaf 2)
let t2 = Node (5, Leaf 4, t1)
let t1 = Leaf 7

0x01 Immutability 特性(不可变特性)

"值一旦定义就不会改变"

这实际上是 F# 的核心特性,也挺好的,至少我们不必担心某个值在程序的其他地方被意外地修改。

let x = 12
let y = x + 1

x 被定义为 12,y 被定义为 x + 1,即 13,也不会被改变。

x:= 12,\, \, y:= x+1 \, \, \, \, \Rightarrow\, \, \, y=13 

如果我们需要表示和操作类似于你在问题中提到的数值表达式,可以使用 可辨识联合类型

type Exp =
    | Num of int
    | Var of string
    | Add of Exp * Exp
    | Sub of Exp * Exp
    | Mul of Exp * Exp
    | Div of Exp * Exp

let e = Mul (Num 3, Add (Var "x", Num 1))

这个 Exp 就是一个 递归记录类型 (discriminated union),它可以是一个整数,一个变量,或者是两个表达式相加、相减、相乘或者相除。

每一个 Exp 类型的值都是不可变的。一旦你创建了 e,它的结构和值都不能改变。

这种不可变性有助于编写健壮且容易理解的代码。

为了演示如何在 F# 中对这种表达式求值,我们举个例子。

假设我们有一个上下文(例如,一个字典)来存储变量的值:

let rec eval (exp: Exp) (context: Map<string, int>) =
    match exp with
    | Num n -> n
    | Var v -> context.[v]
    | Add (e1, e2) -> eval e1 context + eval e2 context
    | Sub (e1, e2) -> eval e1 context - eval e2 context
    | Mul (e1, e2) -> eval e1 context * eval e2 context
    | Div (e1, e2) -> eval e1 context / eval e2 context

// 创建一个上下文
let context = Map.ofList [("x", 2)]

// 计算表达式 e 的值
let result = eval e context

printfn "The result of the expression is %d" result

这就展示了如何在 F# 中处理不可变的数据结构,并使用递归函数来操作这些数据结构。

eval 是一个递归函数,接受一个表达式和一个上下文(变量值的映射)。

根据表达式的类型进行模式匹配,并计算出结果。

字典中存储了 x:= 2,通过 eval e context 计算出表达式 e 的值为 3*(2+1)=9

0x01 举例:语法树

让我们定义一个类型来表示数值表达式,我们在讲座中已经看到了一个类似的语言定义,附带问题:我们如何在 C 或 C++ 中表示这种类型?

type Exp =
    Num of int
  | Var of string
  | Add of Exp * Exp
  | Sub of Exp * Exp
  | Mul of Exp * Exp
  | Div of Exp * Exp

let e = Mul (Num 3, (Add (Var "x", Num 1))

3*(x+1)  的语法树:

在 C/C++中,我们可以使用结构体来表示这个类型,然后通过指针来建立树形结构。

#include <iostream>
#include <string>

struct Exp {
    enum class Type {
        NUM,
        VAR,
        ADD,
        SUB,
        MUL,
        DIV
    };

    Type type;
    int numValue;  // 仅在类型为 NUM 时有效
    std::string varName;  // 仅在类型为 VAR 时有效
    Exp* left;   // 左子表达式
    Exp* right;  // 右子表达式
};

// 创建一个新的表达式节点
Exp* makeNum(int value) {
    Exp* exp = new Exp;
    exp->type = Exp::Type::NUM;
    exp->numValue = value;
    exp->left = nullptr;
    exp->right = nullptr;
    return exp;
}

Exp* makeVar(const std::string& name) {
    Exp* exp = new Exp;
    exp->type = Exp::Type::VAR;
    exp->varName = name;
    exp->left = nullptr;
    exp->right = nullptr;
    return exp;
}

Exp* makeAdd(Exp* left, Exp* right) {
    Exp* exp = new Exp;
    exp->type = Exp::Type::ADD;
    exp->left = left;
    exp->right = right;
    return exp;
}

Exp* makeSub(Exp* left, Exp* right) {
    Exp* exp = new Exp;
    exp->type = Exp::Type::SUB;
    exp->left = left;
    exp->right = right;
    return exp;
}

Exp* makeMul(Exp* left, Exp* right) {
    Exp* exp = new Exp;
    exp->type = Exp::Type::MUL;
    exp->left = left;
    exp->right = right;
    return exp;
}

Exp* makeDiv(Exp* left, Exp* right) {
    Exp* exp = new Exp;
    exp->type = Exp::Type::DIV;
    exp->left = left;
    exp->right = right;
    return exp;
}

// 释放表达式树的内存
void freeExp(Exp* exp) {
    if (exp) {
        freeExp(exp->left);
        freeExp(exp->right);
        delete exp;
    }
}

int main() {
    // 创建示例表达式
    Exp* e = makeMul(makeNum(3), makeAdd(makeVar("x"), makeNum(1)));

    // 打印表达式类型
    switch (e->type) {
        case Exp::Type::NUM:
            std::cout << "Num: " << e->numValue << std::endl;
            break;
        case Exp::Type::VAR:
            std::cout << "Var: " << e->varName << std::endl;
            break;
        case Exp::Type::ADD:
            std::cout << "Add" << std::endl;
            break;
        case Exp::Type::SUB:
            std::cout << "Sub" << std::endl;
            break;
        case Exp::Type::MUL:
            std::cout << "Mul" << std::endl;
            break;
        case Exp::Type::DIV:
            std::cout << "Div" << std::endl;
            break;
    }

    // 释放内存
    freeExp(e);

    return 0;
}

我们使用了一个结构体来表示表达式的不同部分,并且定义了函数来创建和释放表达式树。这样可以在内存中构建出一个对应于给定表达式的树形结构,并且能够在使用完后释放这些内存。


📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2024.6.5
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1792216.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode刷题之HOT100之搜索旋转排序数组

2024/6/2 雨一直下&#xff0c;一个上午都在床上趴着看完了《百年孤独》&#xff0c;撑伞去吃了个饭&#xff0c;又回到了宿舍。打开许久未开的老电脑&#xff0c;准备做题了。《百年孤独》讲了什么&#xff0c;想表达什么&#xff0c;想给读者留下什么&#xff0c;我不知道&am…

IP黑名单与IP白名单是什么?

在IP代理使用中&#xff0c;我们经常听到黑名单与白名单两个名词&#xff0c;它们不仅提供了强大的防御机制&#xff0c;还可以灵活应对不同的安全威胁。本文将详细探讨IP黑名单和白名单在网络安全中的双重屏障作用。 一、IP黑名单和白名单定义 IP黑名单与IP白名单是网络安全中…

2024会声会影全新旗舰版,下载体验!

在当今数字时代&#xff0c;视频内容已成为最受欢迎的媒介之一。无论是个人娱乐、教育还是商业推广&#xff0c;优秀的视频制作都是吸引观众的关键。为了满足广大用户对高质量视频制作软件的需求&#xff0c;我们隆重推出了会声会影2024最新旗舰版。这款软件不仅集成了最先进的…

六、SQL执行器的定义和实现

之前的Sql执行都是耦合在SqlSession里的&#xff0c;现在要对这部分进行解耦和重构&#xff0c;引申出执行器&#xff0c;查了相关概念&#xff0c;Executor执行器可以说是定义了一个个的SQL的执行流程&#xff0c;用查询方法举例&#xff0c;大概一下几步&#xff1a; 1.获取数…

基于SOA海鸥优化算法的三维曲面最高点搜索matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于SOA海鸥优化算法的三维曲面最高点搜索matlab仿真&#xff0c;输出收敛曲线以及三维曲面最高点搜索结果。 2.测试软件版本以及运行结果展示 MATLAB2022A版本…

OPPO 文件传输 - 将文件从 OPPO 手机传输到 PC 的 5 种方法

OPPO手机以其出色的拍照功能而闻名&#xff0c;尤其是新推出的OPPO Find X2系列&#xff0c;它配备了高清前置镜头和超夜景模式&#xff0c;让您轻松拍出精彩瞬间。当您需要将这些照片或其他文件从OPPO手机传输到PC时&#xff0c;以下是五种简便的方法。 第 1 部分&#xff…

掘金AI商战宝典-高阶班:如何用AI制作视频(11节视频课)

课程下载&#xff1a;掘金AI商战宝典-高阶班&#xff1a;如何用AI制作视频(11节视频课)-课程网盘链接提取码下载.txt资源-CSDN文库 更多资源下载&#xff1a;关注我。 课程目录&#xff1a; 1-第一讲用AI自动做视频(上)_1.mp4 2-第二讲用AI自动做视频(中)_1.mp4 3-第四讲A…

你真的理解补码了吗?

下面来看三句话&#xff1a; &#xff08;1&#xff09;一个数的补码等于原码取反加1 &#xff08;2&#xff09;减去一个数等于加这个数的补码 &#xff08;3&#xff09;一个数的反码就是这个数原码的每一位都取反 学过补码的同学应该都听过类似的表述&#xff0c;如果你…

使用迁移助手 (SSMA for Oracle) 将Oracle19c数据库迁移到SQL Server2022

如何使用适用于 Oracle 的 SQL Server 迁移助手Microsoft SQL Server Migration Assistant for Oracle (SSMA for Oracle) 将 Oracle 数据库迁移到 SQL Server Microsoft SQL Server Migration Assistant (SSMA) for Oracle is a tool to automate migration from Oracle data…

python数据分析——模型诊断

参考资料&#xff1a;活用pandas库 创建模型是持续性活动。当向模型中添加或删除变量时&#xff0c;需要设法比较模型&#xff0c;并需要统一的方法衡量模型的性能。 1、残差 模型的残差指实际观测值与模型估计值之差。 # 导入pandas库 import pandas as pd # 读取数据集 hou…

学Python,看一篇就够

学Python&#xff0c;看一篇就够 python基础注释变量标识符命名规则使用变量认识bugDebug工具打断点 数据类型输出转义字符输入输入语法输入的特点 转换数据类型pycharm交互运算符的分类赋值运算符复合赋值运算符比较运算符逻辑运算符拓展 条件语句单分支语法多分支语法拓展 if…

JavaScript 学习笔记 总结

回顾&#xff1a; Web页面标准 页面结构&#xff1a;HTML4、HTML5页面外观和布局&#xff1a;CSS页面行为&#xff1a;JavaScript强调三者的分离前后端分离开发模式 响应式设计Bootstrap框架入门 Bootstrap总结 基础 下载和使用基础样式&#xff1a;文本样式、图片样式、表格…

模式识别涉及的常用算法

一、线性回归 1.算法执行流程&#xff1a; 算法的执行流程可以简述如下&#xff1a; 导入必要的库&#xff1a; 导入NumPy库&#xff0c;用于数值计算。导入Matplotlib库&#xff0c;用于数据可视化。导入Pandas库&#xff0c;用于数据处理&#xff08;尽管在这个例子中&#…

SpringBoot定时任务+Quartz 动态调度

1、分部解释 2、完整代码 3、SpringBoot定时任务Quartz 1、动态定时任务&#xff1a; 动态定时任务&#xff0c;即定时任务的动态调度&#xff0c;可根据需求自由的进行任务的生成、暂停、恢复、删除和更新操作。Quartz本身没有提供动态调度的功能,需要自己根据相关的API开发。…

Nvidia Jetson/Orin +FPGA+AI大算力边缘计算盒子:美团小袋自动配送车

大型电商公司美团已选用NVIDIA Jetson AGX Xavier 平台&#xff0c;作为无人配送机器人核心AI算力。 美团点评是全球大型的按需食品配送公司&#xff0c;结合了Uber Eats、Yelp和Groupon的商业模式&#xff0c;与超过40万家本地企业开展合作。他们推出了小袋自动配送车&#…

Hive3.1.2分区与排序(内置函数)

Hive3.1.2分区与排序&#xff08;内置函数&#xff09; 1、Hive分区(十分重要&#xff01;&#xff01;) 分区的目的&#xff1a;避免全表扫描&#xff0c;加快查询速度&#xff01; 在大数据中&#xff0c;最常见的一种思想就是分治&#xff0c;我们可以把大的文件切割划分成…

【InternLM实战营第二期笔记】05:LMDeploy 量化部署 LLM 实践

文章目录 课程背景常见部署方法LMDeploy安装、部署、量化量化默认比例 KV cachecache-max-entry-count0.5cache-max-entry-count0.014bit 量化 Serve a model启动服务链接 API 服务器网页客户端访问服务器 API 代码集成Python 代码运行 1.8B 模型向 TurboMind 后端传递参数 拓展…

AOP案例

黑马程序员JavaWeb开发教程 文章目录 一、案例1.1 案例1.2 步骤1.2.1 准备1.2.2 编码 一、案例 1.1 案例 将之前案例中增、删、改相关节后的操作日志记录到数据库表中。 操作日志&#xff1a;日志信息包含&#xff1a;操作人、操作时间、执行方法的全类名、执行方法名、方法…

苍穹外卖笔记-02-借助小乌龟创建gitee仓库,apifox代替YApi,Swagger

TOC 1 借助小乌龟创建gitee苍穹外卖仓库 这里建议看视频bilibili比特鹏哥视频 使用软件 git TortoiseGit https://git-scm.com/downloads https://tortoisegit.org/ 使用代码托管平台gitee&#xff0c;git的使用和gitee的账号创建需要查询其他资料 在一个从未克隆仓库的…

yolov8-obb 旋转目标检测 瑞芯微RKNN芯片部署、地平线Horizon芯片部署、TensorRT部署

特别说明&#xff1a;参考官方开源的yolov8代码、瑞芯微官方文档、地平线的官方文档&#xff0c;如有侵权告知删&#xff0c;谢谢。 模型和完整仿真测试代码&#xff0c;放在github上参考链接 模型和代码。 折腾旋转目标检测的小伙伴们看过来&#xff0c;yolov8旋转目标检测部署…