【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 * Explet 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 contextprintfn "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 * Explet 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.mzph.cn/bicheng/23195.shtml

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

相关文章

Android音频API介绍

Android系统提供了四个层面的音频API&#xff1a; Java层MediaRecorder&MediaPlayer系列&#xff1b;Java层AudioTrack&AudioRecorder系列&#xff1b;Jni层opensles&#xff1b;JNI层AAudio&#xff08;Android O引入&#xff09; 下面分别介绍这些API的使用及特点。…

Vulnhub-DC-2

靶机IP:192.168.20.135 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) kaliIP:192.168.20.128 扫描靶机端口及服务版本 发现开放了80和7744端口 并且是wordpress建站 dirsearch扫描目录 访问前端界面&#xff0c;发现存在重定向 在hosts文件中增加192.168.2…

QT4-QT5升级(3)GBK-UTF-8-乱码“常量中有换行符”

乱码有两种&#xff1a;我命名为汉字乱码菱形乱码如下&#xff1a; 1.文件编码为&#xff1a; GB2312 打开编码&#xff1a; GB2312 编译后&#xff1a; QString 部分字符串 常量中有换行符 char * …

RocketMq源码解析五:生产者Producer发送消息

上一章我们把生产者启动的流程和大家一起跟着源码走了一遍,现在我们来看发送消息的流程。上一章我们已经把核心接口和类关系梳理了一遍。如下图 我们今天重点看MQProducer中的send方法最终的实现。DefaultMQProducer中,send的实现最终还是调用了 defaultMQProducerIm…

微信小程序-案例:本地生活-首页(不使用网络数据请求)

一、 1.页面效果&#xff1a; 二、 1.新建项目并添加页面 在app.json文件中&#xff1a; "pages": ["pages/home/home","pages/message/message","pages/contact/contact"] 2.配置导航栏效果 在app.json文件中&#xff1a; &quo…

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

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

趣测小程序开发搭建,趣测趣玩小程序是何物?

一、趣测小程序简介 趣测趣玩小程序是一款提供趣味测试和玩乐功能的应用程序。用户可以通过该小程序参与各种有趣的测试&#xff0c;这些测试可能涵盖性格、情感、智力等多个方面&#xff0c;旨在为用户提供轻松愉快的体验。同时&#xff0c;该小程序还可能包含一些游戏元素&a…

8086 汇编笔记(十):标志寄存器

前言 一、ZF 标志 Zero Flag&#xff0c;零标记位。用于记录相关指令执行后&#xff0c;其结果是否为 0。如果结果为 0&#xff0c;则 ZF1&#xff0c;如果结果非 0&#xff0c;则 ZF0 mov ax,1 sub ax,1 ;ZF 1 mov ax,2 sub ax,1 ;ZF0 二、PF 标志 Parity Flag&a…

AutoCAD记录

esc&#xff1a;取消&#xff08;取消操作&#xff09; L空格&#xff1a;画直线 C空格&#xff1a;画圆 &#xff08;软件提供了圆的多种画法&#xff0c;其中包括与其他图形的切线&#xff09; A空格&#xff1a;画圆弧 tab&#xff1a;切换数据输入框&#xff08;如下图…

Renesas MCU之串口的发送接收功能实现

目录 概述 1 软硬件介绍 1.1 软件版本信息 1.2 硬件介绍 2 FSP配置项目 2.1 项目参数配置 2.2 配置UART参数 3 功能实现 3.1 软件架构实现 3.2 UART接口函数 3.2.1 R_SCI_UART_Open() 3.2.2 R_SCI_UART_Close() 3.2.3 R_SCI_UART_Read() 3.2.4 R_SCI_UART_Writ…

java自学阶段二:JavaWeb开发50(Spring和Springboot学习)

Spring、Springboot基础知识学习 目录 学习目标Spring基础概念IOC控制反转DI依赖注入事务管理AOP面向切面编程Spring案例说明&#xff08;Postman使用、Restful开发规范、lombok、Restful、nginx了解&#xff09; 一&#xff1a;学习目标&#xff1a; 1&#xff09;了解Sprin…

如何解决chatgpt出现503 bad gateway的问题

昨日&#xff0c;ChatGPT官网挂了&#xff0c;也就是使用web网页端访问的用户&#xff0c;会出现 bad gateway 情况。我们去ChatGPT官方的监控查看&#xff0c;已经展示相关错误。 影响的范围有&#xff1a; 影响了 ChatGPT 所有计划的所有用户。影响包括所有与 ChatGPT 相关…

React:Expected property name or ‘}‘ in JSON at position 1

代码&#xff1a; import { Form, Input, Button } from antd export default function FormCom() {function onFinish(a, b, c, d) {console.log(a, b, c, d)}const describe "{tip:请输入用户名}"return (<><Form onFinish{onFinish}><Form.Itemn…

1V1音视频实时互动直播系统

李超老师的项目 先肯定分为两个两个端&#xff0c;一个是服务器端一个是客户端。客户端用于UI界面的显示&#xff0c;服务器端用于处理客户端发来的消息。 我们先搭建stun和turn服务器 首先介绍一下什么是stun协议&#xff0c; 它是用来干什么的&#xff1f; stun协议存在…

Nginx网站服务【☆☆☆】

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

MacOS 安装C语言版TensorFlow

文章目录 安装C语言版TensorFlow解压归档环境变量c_api.hC语言示例 安装C语言版TensorFlow 官方文档&#xff1a;https://tensorflow.google.cn/install/lang_c?hlzh-cnTensorFlow 提供了一个 C API&#xff0c;该 API 可用于为其他语言构建绑定。该 API 在 c_api.h 中定义&a…

从C到C++,C++入门(2)

在C入门篇&#xff08;1&#xff09;中&#xff0c;博主为大家简单介绍了什么是C&#xff0c;以及C中的关键字&#xff0c;命名空间&#xff0c;输入与输出和缺省参数的相关知识。今天就让我们继续一起学习C的基础知识点吧&#xff01;&#xff01; 1.函数重载 1.1函数重载的概…

经典的泡泡龙游戏源码免费下载

源码介绍 HTML5泡泡龙冒险小游戏是一款休闲网页游戏&#xff0c;游戏玩法是玩家从下方中央的弹珠发射台射出彩珠&#xff0c;多于3个同色珠相连则会消失。 源码下载 经典的泡泡龙游戏源码免费下载

C# WPF入门学习主线篇(六)—— TextBox常见属性和事件

欢迎回到C# WPF入门学习系列的第六篇。在前面的文章中&#xff0c;我们探讨了按钮&#xff08;Button&#xff09;的事件处理。今天&#xff0c;我们将继续学习另一个常用的WPF控件——TextBox。本文将介绍 TextBox 的常见属性和事件&#xff0c;并通过示例代码展示如何在实际应…

企业办公网安全管控挑战与解决方案

在数字化浪潮的推动下&#xff0c;企业正经历前所未有的变革。然而&#xff0c;随之而来的是一系列复杂的网络安全风险和挑战。我们的网络边界不再清晰&#xff0c;各种设备轻松接入企业网络&#xff0c;这不仅带来了便利&#xff0c;也极大地增加了安全风险。想象一下&#xf…