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

  

  • 💭 写在前面:本文旨在探讨不可变数据结构在 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]. []. .

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

适合航天航空的国产FTP替代软件

在宇宙探索的旅程中&#xff0c;航空和航天领域总是站在科技的最前沿&#xff0c;对数据传输的要求特别高。随着信息量急剧增加和安全威胁的复杂化&#xff0c;传统的FTP软件已经不能满足这个高端领域的需要了。因此&#xff0c;找到一款适合航空和航天领域的FTP替代软件&#…

spring 解决循环依赖

在 spring 框架中&#xff0c;我们知道它是通过三级缓存来解决循环依赖的&#xff0c;那么它具体是怎么实现的&#xff0c;以及是否必须需要三级缓存才能解决循环依赖&#xff0c;本文来作相关介绍。 具体实现 先来看看它的三级缓存到底是什么&#xff0c;先看如下代码&#…

Nginx网站服务【☆☆☆】

市面上常用Linux的web服务器&#xff1a;apache、Nginx。 apache与nginx的区别&#xff1f; 最核心的区别在于NGINX采用异步非阻塞机制&#xff0c;多个连接可以对应一个进程&#xff1b;apache采用的是同步阻塞多进程/线程模型&#xff0c;一个连接对应一个进程。apache美国…

c++简略实现共享智能指针Shared_Ptr<T>

重点&#xff1a; 1.引用计数在堆上&#xff08;原本应为原子变量&#xff09; 2.引用计数增加减少需要加锁保证线程安全。 3.内部实现Release函数用于释放资源 4.未实现&#xff0c;增加自定义删除器可以将Release修改为模板函数&#xff0c;传入可调用参数。对于shared_p…

tomcat服务器之maxHttpHeaderSize

背景&#xff1a;在OA流程表单中&#xff0c;填写了200条数据&#xff0c;一提交&#xff0c;秒报400错误&#xff0c;且请求没有打到后端中&#xff08;无报错日志&#xff09;&#xff0c;一开始以为是谷歌浏览器的问题&#xff0c;可百度上关于这个错误的解决方案都是清除缓…

docker实战流程:

Docker-compose是docker官方的开源项目&#xff0c;负责实现对docker容器的集群的快速编排&#xff08;通过yaml文件docker-compose.yml管理写好容器之间的调用关系只需一个命令就能实现容器的通识开启或关闭&#xff09;。 类比spring容器&#xff0c;spring管理的是bean而do…

vue3+uniapp

1.页面滚动 2.图片懒加载 3.安全区域 4.返回顶部&#xff0c;刷新页面 5.grid布局 place-self: center; 6.模糊效果 7.缩放 8.微信小程序联系客服 9.拨打电话 10.穿透 11.盒子宽度 12.一般文字以及盒子阴影 13.选中文字 14.顶部安全距离 15.onLoad周期函数在setup语法糖执行后…

PVE安装虚拟主机

本文记录PVE安装其他虚拟主机的步骤&#xff0c;以安装win-server为例。裸机安装PVE则不是本文主题。 准备文件 获取Windows系统镜像 win server镜像可以从官网获取普通Windows镜像可从MSDN获取此外&#xff0c;安装Windows系统还需要从PVE下载特殊驱动 获取Windows必要驱动 …

数据库(25)——多表关系介绍

在项目开发中&#xff0c;进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;各个表之间的结构基本上分为三种&#xff1a;一对多&#xff0c;多对多&#xff0c;一对一。 一对多 例如&#xff0c;一个学校可以有…

PromptPerfect:AI Prompt生成与优化专家

PromptPerfect&#xff1a;AI Prompt生成与优化专家 PromptPerfect是一款专业的AI Prompt生成与优化工具&#xff0c;旨在帮助用户解锁和最大化GPT-4、ChatGPT、Midjourney等先进AI模型的潜力。它通过智能生成和精细优化Prompt&#xff0c;帮助用户获得更精确、更相关、更高效…

Aws EC2,kubeadm方式安装kubernetes(k8s)

版本 docker版本&#xff1a;20.10.25 k8s版本&#xff08;kubeadm&#xff0c;kubelet和kubectl&#xff09;&#xff1a;1.20.10-0 初始化 # 禁用 SELinux sudo setenforce 0 sudo sed -i s/^SELINUXenforcing$/SELINUXpermissive/ /etc/selinux/config# 关闭防火墙 sudo …

题号:BC19 题目:反向输出一个四位数

题号&#xff1a;BC19 题目&#xff1a;反向输出一个四位数 废话不多说&#xff0c;上题目&#xff1a; 解题思路&#xff1a; 我们发现可以用%和/两个操作符就可以解决。 代码如下: int main() {int a 0;scanf("%d ",& a);while (a){printf("%d "…

【实物+仿真设计】基于单片机的物流皮带传输监控系统设计

《基于单片机的物流皮带传输监控系统设计 实物仿真》 整体功能&#xff1a; 本设计采用以单片机为核心控制器&#xff0c;以及传感器检测部分作为输入部分&#xff0c;以报警、显示、洒水、排烟、电机停止模块作为输出部分&#xff0c;构成整个物流皮带传输监控系统。 本设计…

Kolmogorov–Arnold Networks (KAN) 即将改变 AI 世界

目录 一、说明 二、KAN介绍 2.1 什么是 Kolmogorov-Arnold Networks &#xff08;KAN&#xff09;&#xff1a; 2.2 KAN 的秘诀&#xff0c;Splines&#xff01; 2.3 了解KAN工作的最简单方法 三、KAN的主要优点 四、KAN 的 Python 实现 &#xff08;PyKAN&#xff09; 4.1 …

神经网络 | 深度学习背后的数学

神经网分析 机器学习处理的是数据&#xff0c;通过学习输入的数据&#xff0c;从而建立模型&#xff0c;以便预测新的数据的输出 按照类型可以进行如下分类 监督分类 非监督分类 强化学习 神经元 生物学中&#xff0c;人的大脑是由多个神经元互相连接形成网络而构成的。也…

访问vue乱码解决

前情提要&#xff1a; 前端 vue工程npm run build生成的dist静态文件 后端 springboot 后端mvn install 生成war包&#xff0c;前端dist放进war包 报错&#xff1a; 页面访问白屏 报错如图 可以看到chunk-vendors.xxxx.js出现了中文乱码&#xff0c;这个js文件是vue build生成的…

2024版本---LabVIEW 软件安装及使用教程

目录 第1章 LabVIEW 软件安装及使用教程 1. 简介 2. 安装教程 2.1 下载 LabVIEW 2024 版本 2.2 安装 LabVIEW 3. 激活 LabVIEW 4. LabVIEW 基本使用教程 4.1 用户界面介绍 4.2 创建一个简单的 VI&#xff08;虚拟仪器&#xff09; 4.3 数据采集示例 5. 进阶功能介绍…

秋招突击——算法打卡——6/5——提高{(状态机模型)股票买卖、(单调队列优化DP)最大子序列和}——新做:{考试的最大困扰度}

文章目录 提高(状态机模型)股票买卖IV思路分析实现代码参考代码 新作考试的最大困扰度个人实现参考思路 总结 提高 (状态机模型)股票买卖IV 上一次的思路总结&#xff0c;上次写的时候忘记总结了&#xff0c;现在重新画一下图 思路分析 这道题是一个经典的状态机模型&#…

【数据挖掘】学习笔记

文章目录 第一章 引论1.1 为什么进行数据挖掘1.2 什么是数据挖掘&#xff1f;1.3 可以挖掘什么类型的数据1.4 可以挖掘什么类型的模式1.4.1 类/概念描述&#xff1a;特征化和区分1.4.2 挖掘频繁模式、关联规则和相关性1.4.3 用于预测分析的分类和回归1.4.4 聚类分析1.4.5 离群点…

【C语言之排序】-------六大排序

作者主页&#xff1a;作者主页 数据结构专栏&#xff1a;数据结构 创作时间 &#xff1a;2024年5月18日 前言&#xff1a; 今天我们就给大家带来几种排序的讲解&#xff0c;包括冒泡排序&#xff0c;插入排序&#xff0c;希尔排序&#xff0c;选择排序&#xff0c;堆排序&…