F#奇妙游(26):计算表达式浅尝

computation expression之一问三不知

计算表达式是一个有点难理解的东西。我把帮助全部看了一遍,记住了个上下文敏感的计算(contex-sensitive computation)。但是让我讲计算表达式是什么?为什么?怎么做?我是满头雾水。我大概知道是什么,就是一个语法特征,在一个表达式上下文中,使用诸如let!return这些语法,实现与上下文紧密相关的特殊计算。我甚至还能侃侃而谈上下文相关、如何构造某个特殊表达式,match!let!的语法糖之类的。

到底为什么?我不能回答。

奇妙的option计算

还是让我们从一个实际的问题出发。我教了一个班,只有两个学生我有点点满意,一个叫isaac,一个叫mike,其他人我根本不想理会。要实现一个全班评价的函数。

let rateStudent name =match name with| "isaac" -> Some 90| "mike" -> Some 80| _ -> None

这个函数的ADT就是:val rateCustomer: name: string -> int option

下面的问题是:有两个学生,问我给他们评价的总分是多少?我这个人比较龟毛,只有有一个学生是我不想理会的,那么我就不置可否:None

实现起来看起来也挺简单:

let commentTwo name1 name2 = let result1 = rateStudent name1let result2 = rateStudent name2match result1, result2 with| Some r1, Some r2 -> Some(r1 + r2)| _ -> None  

或者,我们在学会一个Option.bind之后,还能写成:

let commentTwoAlt name1 name2 = let result1 = rateStudent name1let result2 = rateStudent name2result1 |> Option.bind (fun x ->result2 |> Option.bind (fun y ->Some (x + Y)))

这里面,我们的Option.bind的原型就是在MSDN中给出为bind f inp = match inp with None -> None | Some x -> f x

这两种方式都行,如果两个学生都是我认可的,那么给出答案Some 170,其他任何情况,我都是不予理会:None

接下来事情可能变得有趣起来。假设我有100个学生,其中50个我觉得差强人意,评了个分数,其他的50个继续是不予理会。

怎么办?如果我不是求两个学生的和,而是要进行其他的计算,怎么办?

Computation Expression版龟毛老师

这里的问题挺简单,就是要处理int option和一个返回int option的函数rateStudent

首先,我们定义一个Builder。

type Maybe() =member this.Bind(opt, func) = opt |> Option.bind funcmember this.Return v = Some v

这个Builder定义了两个操作,一个是绑定,一个是返回。然后实例化一个builder。

let maybe = Maybe()

接下来就能开心的用计算表达式来处理任意复杂的计算:

let answer =maybe {// binding int option to int let! first = rateStudent "isaac"let! second = rateStudent "mike"let! third = rateStudent "isaac"// calculation using intlet total = first + second + third// return float option from floatreturn (float total) / 3.0}

上面这个值,是一个float option,当我们计算中任何一个rateStudent返回None,计算表达式就会马上输出None。

我们在上面插入一些printfn,更改rateStudent的参数,就会发现,只要任何一个地方出现None的绑定,马上就返回None

let answer =maybe {printfn "0"let! first = rateStudent "isaac"printfn "1"let! second = rateStudent "mike"printfn "2"let! third = rateStudent "isaac"printfn "3"let total = first + second + thirdprintfn "return"return (float total) / 3.0}

如果第一个名字isaac打错的话,就只会打印0,把None绑定到answer,非常神奇。

优点

上面这种实现的优点有哪些?

  1. 在maybe中,let!绑定变量的类型是int而不是int option;
  2. 后续的计算,完全不用处理option的问题,就当做是没有option(龟毛老师)这回事;
  3. 返回的类型是float,而不需要写作Some float。

疑点

这里的Return比较容易理解,就是把一个值包装成Some。但是Bind是怎么工作的呢?我们看看Maybe的定义:

type Maybe() =member this.Bind(opt, func) = opt |> Option.bind func

这里我们调用的时候let! first = rateStudent "isaac",第一个值好理解,是一个int option,刚好符合Option.bind的第一个参数。但是第二个参数func是什么呢?

这里的疑问其实是let在F#中特殊用途。按照函数式编程的概念,并没有什么全局变量的概念。而let看起来是定义了一个全局变量。

let x = 10
printfn "%A" x

定义变量,访问变量,多么熟悉啊。但是这里的let,其实是一个语法糖,它的本质是一个函数调用。

let x = 10 in 
printfn "%A" x

或者

let x = 10 in printfn "%A" x

本质上,是:

10 |> (fun x -> printfn "%A" x)

这样写就清楚多了。

那么我们这下就能够明白,为什么定义绑定的时候,第二个参数是一个函数了。因为let!的本质是一个函数调用,而Option.bind的第二个参数就是一个函数。实际上,这里的函数实际上就是后续的所有表达式组成的一个函数。

let answer =maybe {let! first = rateStudent "isaac" inlet! second = rateStudent "mike" inlet! third = rateStudent "isaac" inlet total = first + second + third inreturn (float total) / 3.0}

或者

    maybe.Bind (rateStudent "isaac")  (fun first ->maybe.Bind (rateStudent "mike") (fun second ->maybe.Bind (rateStudent "isaac") (fun third ->let total = first + second + thirdreturn (float total) / 3.0)))

这样的话,我们就更清楚Bind函数的表达式为什么是那么样子,而上面的计算表达式中,firstsecondthirdtotal都是int,而不是int option了。

结论

  1. 处理int option的Builder,把int option的计算表达式,转换成了int的计算表达式;
  2. 这就是计算表达式的作用,让我们在上下文中编写表达式,表达我们所想要关注的计算,把option的处理逻辑放在上下文中隐含处理;
  3. 这里只涉及到两个表达式let!return,更多的构造,接下来再说。

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

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

相关文章

RSA加密算法

RSA原理 RSA基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,…

MonoDETR: Depth-guided Transformer for Monocular 3D Object Detection 论文解读

文章目录 1.Abstract2. Introduction3. Related workDETR base methods 4. Method4.1Feature ExtractionVisual Featuresdepth featuresforeground depth map 4. 2 Depth guided transformerVisual and depth encodersDepth-guided-decoderDepth positional encoding 4. 3 Dete…

【vue2第十一章】v-model的原理详解 与 如何使用v-model对父子组件的value绑定 和修饰符.sync

v-model的原理详解 v-model的本质就是一个语法糖,实际上就是 :value"msg" 与 input"msg $event.target.value" 的简写。 :value"msg" 从数据单向绑定到input框,当data数据中的msg内容一旦改变,而input框数据…

OpenCV(十八):图像直方图

目录 1.直方图统计 2.直方图均衡化 3.直方图匹配 1.直方图统计 直方图统计是一种用于分析图像或数据的统计方法,它通过统计每个数值或像素值的频率分布来了解数据的分布情况。 在OpenCV中,可以使用函数cv::calcHist()来计算图像的直方图。 calcHist(…

uni-app之android离线打包

一 AndroidStudio创建项目 1.1,上一节演示了uni-app云打包,下面演示怎样androidStudio离线打包。在AndroidStudio里面新建空项目 1.2,下载uni-app离线SDK,离线SDK主要用于App本地离线打包及扩展原生能力,SDK下载链接h…

电商平台api对接货源

如今,电商平台已经成为了人们购物的主要途径之一。 然而,对于电商平台来说,货源对接一直是一个比较棘手的问题。为了解决这个问题,越来越多的电商平台开始使用API来对接货源。 API,即应用程序接口,是一种允…

分支创建查看切换

1、初始化git目录,创建文件并将其推送到本地库 git init echo "123" > hello.txt git add hello.txt git commit -m "first commit" hello.txt$ git init Initialized empty Git repository in D:/Git/git-demo/.git/ AdministratorDESKT…

计算机视觉传统图像处理库opencv的使用

人工智能领域的图像处理分支,整理了计算机视觉传统图像处理库opencv的使用网址链接。 opencv使用范围,主要用在计算机视觉、视频分析、机器学习、医学影像处理、自动驾驶、工业检测、游戏开发上。 1):opencv效果视频 opencv10个应…

Qt QSlider样式

滑块控件QSlider,如果设置的垂直样式,其进度颜色和剩余颜色,刚好和横向样式的颜色相反的,不确定这个是否是Qt的BUG,Qt456都是这个现象 QSlider::groove:horizontal{ height:8px; background:#FF0000; }QSlider::add-p…

django中配置使用websocket终极解决方案

django ASGI/Channels 启动和 ASGI/daphne的区别 Django ASGI/Channels 是 Django 框架的一个扩展,它提供了异步服务器网关接口(ASGI)协议的支持,以便处理实时应用程序的并发连接。ASGI 是一个用于构建异步 Web 服务器和应用程序…

远程方法调用中间件Dubbo在spring项目中的使用

Dubbo是一个分布式服务框架,它可以帮助我们快速开发和提供高性能、高可靠性的分布式服务,同时提供服务治理、容错、负载均衡等功能。 使用Dubbo可以分为以下步骤: 引入Dubbo依赖:在项目的pom.xml文件中引入Dubbo的依赖。编写服务…

ipad手写笔什么牌子好?适合开学买的电容笔推荐

随着社会和经济水平的不断提高,对电容笔的使用日益增加。国产平替的电容笔和原装苹果的电容笔没有太大的区别,不管是功能还是手感都很像,书写上的笔锋也很流畅,这让我很惊讶,因为其的价格很便宜,但其的体验…

【实训项目】精点考研

1.设计摘要 如果说高考是一次能够改变命运的考试,那么考研应该是另外一次。为什么那么多人都要考研呢?从中国教育在线官方公布是考研动机调查来看,大家扎堆考研的原因大概集中在这6个方面:本科就业压力大,提升竞争力、…

开源照片管理服务LibrePhotos

本文是为了解决网友 赵云遇到的问题,顺便折腾的。虽然软件跑起来了,但是他遇到的问题,超出了老苏的认知。当然最终问题还是得到了解决,不过与 LibrePhotos 无关; 什么是 LibrePhotos ? LibrePhotos 是一个自托管的开源…

HTTP/1.1协议中的响应报文

2023年8月30日,周三下午 目录 概述响应报文示例详述 概述 HTTP/1.1协议的响应报文由以下几个部分组成: 状态行(Status Line)响应头部(Response Headers)空行(Blank Line)响应体&a…

后端面试话术集锦第 十二 篇:java基础部分面试话术

这是后端面试集锦第十二篇博文——java基础部分面试话术❗❗❗ 1. String类中常用的方法 split():把字符串分割成字符串数组 indexOf():从指定字符提取索引位置 trim():去除字符串两端空格 replace():替换 hashCode():返回此字符串的哈希码 subString():截取字符串 equa…

学习高级数据结构:探索平衡树与图的高级算法

文章目录 1. 平衡树:维护数据的平衡与高效性1.1 AVL 树:严格的平衡1.2 红黑树:近似平衡 2. 图的高级算法:建模复杂关系与优化2.1 最小生成树:寻找最优连接方式2.2 拓扑排序:解决依赖关系 拓展思考 &#x1…

支付参考文档

支付宝官方提供的样例代码与支付宝通信: 小程序文档 - 支付宝文档中心支付宝文档中心https://docs.open.alipay.com/api_1/alipay.trade.query 参考sdk示例: 小程序文档 - 支付宝文档中心支付宝文档中心https://docs.open.alipay.com/api_1/alipay.tra…

unity界面上Global 与Local xyz- right up forward

gloabal 如果要沿这个方向移动就比较困难 local下就不一样了

docker高级(DockerFile解析)

1、构建三步骤 编写Dockerfile文件 docker build命令构建镜像 docker run依镜像运行容器实例 2、DockerFile构建过程解析 Dockerfile内容基础知识 1:每条保留字指令都必须为大写字母且后面要跟随至少一个参数 2:指令按照从上到下,顺序执行…