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

简单值的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 intlet create value = if value < 1 then Error "UnitQuantity can not be negative"else if value > 1000 thenError "UnitQuantity can not be more than 1000"elseOk (UnitQuantity value)let value (UnitQuantity uq) = uq

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

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

let result = UnitQuantity.create 1match result with
| Error msg -> printfn "faile to create: %s" msg
| Ok uQty -> printfn "Successful with value : %A" uQtylet iv = UnitQuantity.value uQtyprintfn "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.hqwc.cn/news/98594.html

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

相关文章

没有软件怎么管理固定资产

在当今数字化的世界中&#xff0c;我们已经习惯了使用各种软件来管理我们的日常生活和工作。然而&#xff0c;当我们面临一个看似简单的问题——如何管理固定资产时&#xff0c;我们可能会感到困惑。那么&#xff0c;如果没有软件&#xff0c;我们该如何进行资产管理呢&#xf…

通过idea实现springboot集成mybatys

概述 使用springboot 集成 mybatys后&#xff0c;通过http请求接口&#xff0c;使得通过http请求可以直接直接操作数据库&#xff1b; 完成后端功能框架&#xff1b;前端是准备上小程序&#xff0c;调用https的请求接口用。简单实现后端框架&#xff1b; 详细 springboot 集…

面试被问到:测试计划和测试方案有什么区别?你会回答吗~

面试的时候&#xff0c;很多小伙伴都被面试官问过这个问题 “测试计划和测试方案有什么区别”&#xff1f; 到底有什么区别呢&#xff1f;我们先好好了解下这两个文档。 一、测试计划 1、测试计划是什么 测试计划是组织管理层面的文件&#xff0c;从组织管理的角度对一次测…

终于来啦!OpenDataLab 新增自主上传功能,升级CLI/SDK工具、数据集详情页……体验赠好礼~

9月&#xff0c;OpenDataLab 全新版本上线&#xff0c;支持用户自主发布原创数据集&#xff0c;同时升级了 CLI/SDK 工具、数据集详情页&#xff0c;让 AI 数据集开源更方便、展示更清晰。还有创作领好礼活动&#xff0c;快来看看吧&#xff01; &#xff08;注意&#xff01;…

亚马逊重大更新,底层卖家的机会来了(干货)

最近&#xff0c;亚马逊对产品详细页面进行了又一次的改版。在这次改版中&#xff0c;亚马逊调整了产品详细页面的五点描述显示&#xff0c;无论是在电脑端还是移动端&#xff0c;大部分产品的五点描述都被折叠。 亚马逊的这一举动可能会改变传统的流量分配方式&#xff0c;进一…

(18)线程的实例认识:线程的控制,暂停,继续,停止,线程相互控制,协作

话不多&#xff0c;但比较中肯&#xff0c;本文参照c# 线程暂停继续的实现方式_哔哩哔哩_bilibili 一、老方式 1、这是一个老的实现方式&#xff0c;基本不推荐&#xff0c;背后控制的原理需要了解。 界面&#xff1a;三个button一个textbox …

Qt之事件过滤—筛选处理对象

文章目录 事件过滤完整代码 事件过滤 事件过滤是当事件发生时&#xff0c;可以对不同对象&#xff0c;实现不同操作&#xff0c;以达到筛选的效果。 步骤&#xff1a; 1、首先安装一个事件过滤器&#xff0c;为对象安装事件过滤&#xff0c;指定“谁”来监控这些事件对象 //给…

广告、政府、IT三重合作:凭爱校对轻松搞定文本质量

在广告创意、政府政策和IT开发这三个看似不相关的领域中&#xff0c;有一个共同的需求&#xff1a;高质量的文本内容。本文将探讨如何通过使用“爱校对”工具&#xff0c;在这三个行业内确保文本质量&#xff0c;从而提高工作效率和准确性。 广告行业&#xff1a;语境与创意的完…

全国唯一一所初试考Java的学校!平均300分拿下

苏州科技大学 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1187字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 苏州科技…

SIP mini对讲 SV-10/SV-10W 86型sip对讲终端

SIP mini对讲 SV-10/SV-10W 86型sip对讲终端 A10系列是专门针对室内用户需求研发的一款SIP mini 对讲产品&#xff0c;集智能安防、音频对讲和广播功能于一体&#xff0c;功能强大&#xff0c;性价比高。它外观小巧&#xff0c;支持按键图标/功能自定义&#xff0c;配备3个可编…

【Java转Go】Go中使用WebSocket实现聊天室(私聊+群聊)

目录 前言功能效果&#xff08;一人分饰多角.jpg&#x1f60e;&#xff09;用户上线、群聊私聊和留言下线 实现思路代码服务端 chat.go 完整代码客户端 html 完整代码 最后 前言 之前在Java中&#xff0c;用 springbootwebsocket 实现了一个聊天室&#xff1a;springbootwebso…

python爬虫关于ip代理池的获取和随机生成

前言 在进行爬虫开发时&#xff0c;代理IP池是一个非常重要的概念。代理IP池是指一个包含多个可用代理IP的集合&#xff0c;这些代理IP可以用来绕过网站的防爬虫策略&#xff0c;从而提高爬取数据的成功率。 在本文中&#xff0c;我们将介绍如何获取代理IP池&#xff0c;并且随…