F#奇妙游(28):ADT中简单值的F#实现

news2024/11/29 20:55:32

简单值的ADT

在领域建模中,我们尝尝会遇到一些简单的值,比如人的名字、人的编号、物品的代码。如果过早进行程序设计,这些值很容易就会变成程序设计语言中的基本量,stringint这些,就比如人的标号和物品的编号,很容易就被记录为int。然而根据DDD的原则,我们应该尽可能用领域专家认可的方式来记录设计。

例如:

type PersonID = 
    | PersonID of int

type ObjectID = 
    | ObjectID of int

这种只有一个选项的和类型(OR类型),也可以省略写成type xxx = xxx of int的形式。在dotnet fsi中运行,还是会识别为type ObjectID = | ObjectID of int。这里面,第一个ObjectID是类型名称,第二个ObjectID是选项标签。

这两个量虽然都是用整型数据类型来表示,但是这两个量在领域内是两个不同的概念。

let p1 = PersonID 0
let obj = ObjectID 0

比如,拿这两个值进行比较,系统就会提示错误:

if p1 = obj then printfn "p1 equals obj"
error FS0001: 此表达式应具有类型
    “PersonID”
而此处具有类型
    “ObjectID”

更不用说用这两个值进行算术运算了。这样就很好地保证了DDD/ADT设计中,非法的状态是不可表示的。

因为这两个量的比较和算术运算是没有意义的。

此外,很自然地在调用函数时,如果参数的类型其中一个,另外一个参数也无法被错误的传入。F#的静态类型机制很好地确保了ADT设计中的原始含义。这种含义对于领域专家是至关重要的,虽然两个值都是整数,但是领域专家并不关心在计算机中如何表达,他们关心的是,在领域中的含义。

对于上面的简单类型,要使用其内部的整型变量,也是非常简单的,采用模式匹配就行。

> let (PersonID idn) = p1;;
val id: int = 0

这个时候idn就被绑定到p1对应的整型数据。此外,在定义函数式,也可以非常简单地利用模式匹配绑定(提取)相应的id数值。

> let printPersonId (PersonID idn) = printfn "ID = %d" idn;;
val printPersonId: PersonID -> unit

> printPersonId p1;;
ID = 0
val it: unit = ()

let在绑定变量和定义函数时所采用的模式匹配语法,与前面ADT定义配合十分完美,保证ADT设计与语义的一一对应,又提供了相应的语法糖来使得程序实现过程十分丝滑,堪称完美。

约束

在前面ADT设计中,还提到,领域中通常会限制数据的取值范围(对应ADT的组合数)。那么约束应该如何来实现呢?在F#中,也有很好的语法构造来确保ADT实现的完整性。

例如,在领域分析设计中,我们和领域专家一起分析和建立的领域模型中包含了如下的简单值,并且确认了关于这些值的一些约束。

type WidgetCode = WidgetCode of string  // 以'w'打头,加4个数字
type UnitQuantity = UnitQuantity of int // 1..1000
type KilogramQuantity = KilogramQuantity of decimal // 0.05 ~ 100.00

除了把这些限制条件写在注释和文档里,并在项目中传递,F#实际上提供了很好的语言工具。

因为函数式编程本身的特性,所有的值都是不变的,因此当一个WidgetCode传递过程中,我们并不担心内部的值会变坏(跟面向对象设计中的类的不变性作比较)。因此唯一的可能性就是建立/定义这个值的时候。

我们就可以通过如下的方式来确保相应的限制。

第一步,我们要限制用户使用类型名 值的方式创建这个值。

type UnitQuantity = private UnitQuantity of int

第二步,我们提供一个用来创建这个值的函数。

module UnitQuantity =
    type UnitQuantity = private UnitQuantity of int
    let create value = 
        if value < 1 then 
            Error "UnitQuantity can not be negative"
        else if value > 1000 then
            Error "UnitQuantity can not be more than 1000"
        else
            Ok (UnitQuantity value)
    let value (UnitQuantity uq) = uq

这里要定义一个UnitQuantity.value函数是因为,当隐藏了构造函数后,就没办法在客户代码例使用模式匹配来定义函数和绑定内部的int量。

那么客户端的代码就可以写成这样:

let result = UnitQuantity.create 1

match result with
| Error msg -> printfn "faile to create: %s" msg
| Ok uQty -> 
    printfn "Successful with value : %A" uQty
    let iv = UnitQuantity.value uQty
    printfn "inner value: %i" iv

其实,还能够使用F#定义语言界面的fsi文件来隐藏构造函数,详见MSDN。

性能

当然,采用上面这样ADT描述非常有利于领域专家的理解,对于DDD开发至关重要的就是形成统一的领域模型。并且,在F#中的巧妙和优雅地实现同样能够在编译阶段发现一些与DDD中开发的领域模型不一致的错误。但是,跟使用原始数据类型intstring相比,这样的确会带来性能损失。

因此,你懂的……

在这里插入图片描述

而且,性能的一点点提升,在某些时候就是会让应用变得更加丝滑,用户的体验也的确是可能得到改善。那么.NET的灵活多变、功能全面在这个时候就起到作用了。

[<Struct>]
type UnitQuantity = private UnitQuantity of int

注意,这个语法仅仅能够在F# 4.1之后使用。但是我回头看了一下日历,现在是2023年,F#的版本已经是7.0。哦,那没事了。

这样做之后,可能还是会有结构体访问所带来的性能损失,但是在使用一系列UnitQuantity时内存对齐的特性就相对友好多了。

结论

  1. type xxx = xxx of int这种简单类型的定义对于DDD和ADT来说是常规操作;
  2. F#提供了很好的语法支持来进行ADT的实现。

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

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

相关文章

第十五课 状语从句

文章目录 前言一、时间状语从句时间状语从句&#xff0c;主语谓语宾语 或者 主语谓语宾语时间状语从句时间状语从句&#xff0c;主语系动词表语 或者 主语系动词表语时间状语从句1、when,while,as 引导的时间状语从句when主语谓语&#xff08;宾语&#xff09;状语when主语系动…

混合查询多家快递,快速掌握物流信息

在现代社会&#xff0c;快递服务已成为我们日常生活的重要组成部分。无论是购物还是文件传递&#xff0c;我们都需要快递服务的帮助。然而&#xff0c;不同的快递公司需要不同的查询方法&#xff0c;这无疑增加了我们的查询难度。因此&#xff0c;有没有一种方法可以让我们一次…

RuntimeError: ANTLR version mismatch

规则引擎源码&#xff1a; nemonik/Intellect: DSL and Rules Engine For Python (github.com) 运行程序 Example.py 时报错&#xff1a; RuntimeError: ANTLR version mismatch: The recognizer has been generated with API V0, but this runtime does not support this. …

svn checkout 报 ‘svn: E000061: 执行上下文错误: Connection refused‘

问题 svn: E170013svn: E000061 ➜ svn svn checkout https://xxx.xxx.xxx.xxx:9443/svn/project-xxx/ svn: E170013: Unable to connect to a repository at URL https://xxx.xxx.xxx.xxx:9443/svn/project-xxx svn: E000061: 执行上下文错误: Connection refused链接在浏览…

收入下降,亏损扩大,利润率急剧恶化,蔚来仍充满风险

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 蔚来第二季度财报分析 猛兽财经从蔚来&#xff08;NIO&#xff09;2023年第二季度财报中&#xff0c;获得的最大收获并不是该公司的收入下降或亏损扩大&#xff0c;而是由于价格竞争加剧&#xff0c;中国电动汽车行业整体上…

5700A福禄克FLUKE 5700A多功能校准器

181/2461/8938Fluke 5700A/5720A 高精度多功能校准器 5700A: 世界级标准产品 通过不断的改进、提高&#xff0c;5700A已经演变为5700A系列II。这是福禄克公司生产的、经过大量测试证明、极为可靠的、高精度校准器。5700A已经在全世界的范围被看作是校准器的标准&#xff0c;具有…

使用python对光谱数据进行lorentz峰值拟合(bounds限定拟合参数范围)

1、lorentz峰值拟合 发光光谱是一种用于表征二维半导体材料光学性质的重要技术&#xff0c;它可以反映出材料中的载流子密度、缺陷态、激子束缚能等信息。 由于二维半导体材料的厚度极其薄&#xff0c;其发光信号往往很弱&#xff0c;且受到基底、环境和测量设备等因素的干扰…

1801. 积压订单中的订单总数;1567. 乘积为正数的最长子数组长度;923. 三数之和的多种可能

1801. 积压订单中的订单总数 核心思想&#xff1a;维护一个最小堆sell和一个最大堆buy&#xff0c;然后模拟即可。 1567. 乘积为正数的最长子数组长度 核心思想:动态规划&#xff0c;z表示以当前数字num结尾的乘积为正的最长子数组长度&#xff0c;f表示以当前数字num结尾的乘…

论文开题:成功之门的五大关键策略

研究生、博士生、学者或任何从事研究的人都会面临一个不可避免的环节——论文开题。这一阶段不仅定义了接下来研究的方向&#xff0c;还可能影响到整个项目的成功与否。那么&#xff0c;如何确保你的开题过程能够无瑕通过&#xff0c;还能打动评审人呢&#xff1f;本文将揭示论…

‘无法启动此程序,因为计算机中丢失dll’的多种解决方法分享,最有靠谱的修复方案

当你尝试启动某个程序时&#xff0c;可能会收到类似于"无法启动此程序&#xff0c;因为计算机中丢失DLL"的错误消息。这种错误可能会导致程序无法正常运行&#xff0c;给用户带来不便。在本文中&#xff0c;我们将详细介绍多种解决计算机中丢失DLL的方法&#xff0c;…

rk3568 SDK的buildroot添加package

开发源码工程 首先进入<SDK>/app 目录下&#xff0c;在该目录下创建一个名为“mypackage”的文件夹。 在 mypackage 目录下创建一个.c 源文件 main.c&#xff0c;以及一个 Makefile 文件。 大家可以自己在 main.c 源文件中编写一个简单的测试代码&#xff0c;譬如打印一…

韶音的耳机怎么样,韶音骨传导耳机值得入手吗

韶音关于骨传导耳机的产品在质量方面还是有着不错的表现&#xff0c;其最具代表性的骨传导耳机就是韶音OpenRun Pro&#xff0c;在国产骨传导耳机中是具备了一定的知名度&#xff0c;有着自主研发的声学技术。 最突出的点就在于颜色上多样化&#xff0c;有着经典的黑色&#xf…

Yapi接口一键生成Java代码

Yapi上定义好接口之后,转换成Java代码时费时费力,都是重复劳动,毫无意义,所以有了这个工具把程序员从大量重复劳动中解放出来。 1:修改application.properties yapi.project.token=f1a0ea09031f41e1adfa18a 获取方法如下: yapi.api.interface.ids和yapi.api.cat.id只配置…

爱校对:让法律、医疗、教育行业的文本更加无懈可击

在今天这个信息爆炸的世界里&#xff0c;文本准确性成了法律、医疗和教育这些严谨行业中一个不能忽视的要点。一个小错误可能造成严重的后果&#xff0c;甚至影响人们的生命和事业。这正是为什么更多的专业人士开始选择使用“爱校对”来确保他们的文档、研究和通讯无懈可击。 法…

Vue组件之间传值

聊一聊vue里面组件之间的传值 首先总结一下vue里面传值的几种关系&#xff1a; 如上图所示, A与B、A与C、B与D、C与F组件之间是父子关系&#xff1b; B与C之间是兄弟关系&#xff1b;A与D、A与E之间是隔代关系&#xff1b; D与F是堂兄关系&#xff0c;针对以上关系 我们把组件…

【C++模拟实现】反向迭代器的实现

【C模拟实现】反向迭代器的实现 目录 【C模拟实现】反向迭代器的实现反向迭代器的代码示例反向迭代器的模拟实现要点引入iterator模版参数rbegin()和rend()的实现 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2023.9.5 前言&#xff1a;本篇博客主要介绍反向迭代器的实现&…

数字孪生与GIS:智慧城市的未来之路

数字孪生和地理信息系统&#xff08;GIS&#xff09;是两个在现代科技中崭露头角的概念&#xff0c;它们的融合为智慧城市项目带来了革命性的机会。本文将解释数字孪生为何需要融合GIS&#xff0c;并以智慧城市项目为例进行说明。 数字孪生是一种虚拟模型&#xff0c;它精确地…

财报解读:多品牌故事下,贝泰妮能否持续领航功效护肤?

2023年上半年&#xff0c;在消费品零售大盘整体上行之际&#xff0c;我国化妆品零售市场也实现了回暖。据国家统计局数据&#xff0c;上半年&#xff0c;全国社会消费品零售总额为227588亿元&#xff0c;同比增长8.2%。其中&#xff0c;化妆品零售总额为2071亿元&#xff0c;同…

苹果电脑系统性能检测 Geekbench 6 for Mac

Geekbench 是一款流行的跨平台基准测试工具&#xff0c;用于评估计算机和移动设备的性能。它可以测量处理器、内存、图形处理器和存储设备等硬件的性能&#xff0c;并生成相应的性能评分。 Geekbench 提供了简单易用的用户界面&#xff0c;用户只需点击运行测试即可开始评估设…

攻防世界-Hear-with-your-Eyes

原题 解题思路 是一个没有后缀的文件&#xff0c;题目提示要用眼睛看这段音频&#xff0c;notepad打开文件&#xff0c;没什么东西。 加后缀zip再解压看看。 使用Audacity打开音频文件