从C到Haskell

缘起

开篇之前先说说为啥开始学习Haskell,作为一个主要写C代码的中老年工程师,总觉得写代码有点那么个思维定式,而Haskell是一个和C完全不同的语言,它会迫使你放弃掉习惯了小半辈子的思维方式,可以帮助咱们中老年朋友跳出编程“舒适区”,避免思维定式。

以下内容与广大中老年朋友分享学习中的粗糙简介,大家感兴趣的话,可以留言区交流。

没有赋值语句

某种意义上说,C语言有两个最重要的东西:表达式和赋值,C语言进行数据运算的方式,就是用表达式做一个运算,然后用赋值写入到变量,如此往复。

C语言里面,变量是一个数据的容器,因此可以多次写入(除非特殊说明是不可写入),比如咱们代码可以这么写:

int test(int x)
{x = x + 10;x = x * 2;return x;
}

这里 x + 10 和 x * 2 都是表达式,然后用赋值把表达式计算的结果写入x。显然,后续的写入会覆盖之前的结果。

由于变量可以多次赋值,因此在C代码里面,一个变量名字,在不同的时间,内部的值显然可以是不同的。因此我们分析和调试C代码的时候,就要特别留意次序,也就是搞清楚“来龙去脉”,这也是很多时候写出来bug的根源。

比如这么一段代码。

int test(int x)
{x = x + 10;。。。if (blahblah) x = 0;。。。return x;
}

假设咱们看代码的时候,漏掉了那个if(blahblah),或者咱们误判了blahblah,结果就完全不同了。

那么Haskell呢?

Haskell里面的=不是赋值,而是绑定(binding),啥叫绑定呢?它的含义更像是数学里面,我们说,把什么什么记作什么什么。

比如下面这行Haskell代码:

x = 1

它的意思是,把1给绑定到符号x上面,也就是说,以后咱们提到x的地方,它其实就是1.要注意的是,一个符号只能固定的绑定一个东西,不能重复绑定,如果我们尝试“修改”x的值会怎样?

x = 1
x = 2

Haskell会报错:Multiple declarations of `x'

它意思是说,您老先说x是1,又说x是2,那x到底是啥呢?

很显然,在Haskell,这个x并不代表一个保存数据的地方,不是C语言变量的概念,它只是一个符号,你可以定义这个符号的含义,而且不能自相矛盾,就是说你不能说这个符号既是一个东西,又是另一个东西。

那么如此有什么好处呢?就是在Haskell里面,我们不需要分析代码执行到哪里了,就可以确定一个符号是什么,也很难搞错。因为不管写在哪里,是啥就是啥,一言九鼎。

补充一点,不同的scope,同一个符号可能绑定不同的内容,比如两个函数里面同一个x可以是不同的,不过这个区分显而易见,基本上不可能混淆

数学意义的函数定义

既然没了“赋值”一说,Haskell的函数和C的函数就不一样了,它不是描述一个计算过程,而是定义一个数学意义的函数关系。比如咱们定义一个一元二次多项式函数:

f x = x*x + 2*x + 1

咱们前面说过,Haskell里面=代表左边的东西右边的东西。因此上面这句话的意思就是以x为自变量,计算f这个函数,它x*x + 2*x + 1

这跟数学概念上的函数定义是吻合的。实际上,写法也很像,只是数学上咱们会多个括号,写成 f(x)。

由于=代表一种定义,那么很显然,有些函数在不同情况下,定义是不一样的。比如说斐波那契,当x是0或者1或者>1,情况是不同的。Haskell里面可以分几次定义一个函数。具体来说就是

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

代码的意思显而易见,斐波那契数列,第0个是0,第1个是1,之后的,是前面两个的和。

到这里咱们就能感觉到Haskell和C本质上不同的地方。从根本上说,C程序是直接告诉计算机,你该作甚,一步一步地给你说清楚,计算机不需要“思考”,直接干活拉倒。Haskell则是告诉计算机,哥们我想要什么结果,至于怎么算,你自己琢磨。

咱们再用一个小例子说明如何用Haskell的方式思考问题。假设我想得到一个字符串,内容是n个'X',就是说我想定义一个函数,比如叫做nX,当我调用 nX 5,我希望得到"XXXXX".

如果是C语言,咱们思考的是,计算机一步一步怎么干活,大概就是,咱先弄个空字符串,然后再一个一个字符的增加,如此这般,这般如此。

Haskell里面,我们要换一个思路,我们要想的是,这玩意是什么

nX 0 是什么?显然,应该是空字符串,因为它表达的是0个X.
nX 1是什么?显然,应该是"X",我们也可以这么表达,空字符串基础上,增加一个"X"
nX 2是什么?显然,应该是"XX",我们也可以说是(nX 1)基础上,增加一个"X"
那么一般的,nX n就应该是 nX (n-1)加上一个"X"

nX 0 = ""
nX n = nX (n - 1) ++ "X"

注意到没有,咱们写这个Haskell代码的时候,完全没有思考计算机具体干活的过程。实际上,咱们看着这段Haskell代码,也不晓得编译器最终会编译出什么东西,除非你懂Haskell编译器。但是我们虽然不知道Haskell代码的实际计算过程,我们却很清楚的知道计算结果

所以说写Haskell代码的思维,更像是老板思维,表达清楚自己要什么,让员工,这里就是Haskell编译器,发挥主观能动性想清楚怎么做。甚至说,只要你能定义清楚结果是什么,你就算真的不知道该怎么做才能得到结果,也没关系,因为你是老板,老板为啥需要知道怎么干活?

更强的代码表达能力

由于Haskell代码的重点是表达想要什么,表达能力就非常重要。比如我想表达1到100之间,可以同时被2和3整除的数。

numList = [x | x <- [1 .. 100], x `mod` 2 == 0, x `mod` 3 == 0 ]

这段代码的意思是:

  • 构造一个列表,其元素为 x
  • x属于1到100
  • x对2取模为0(即整除)
  • x对3取模为0(即整除)

很显然,这个代码写的内容,和我们想要的结果,几乎是1:1对应关系。

再比如我想表达一个函数,如果x小于100,那么结果就是100,否则结果就是x

f x = if x < 100 then 100 else x

如果我条件多一些,比如小于100则结果是100,大于200则结果是200,其他原封不动,可以这么写:

f x| x < 100 = 100| x > 200 = 200| otherwise = x

这里的|可以读作“当”,然后你看这个代码和我们想表达的意思,几乎是一一对应。

小结

习惯写C代码,初看Haskell会非常不习惯,实际上主要是思维方式的不一样,但是一旦我们转变了思维方式,就是说,不再思考计算机具体怎么工作,而是思考我到底想要什么,你会马上发现,Haskell代码的表达力,不是一般的强,而且很多时候,可以极大的简化写代码的工作。

One More Thing

Haskell的函数调用,跟大多数其他编程语言比,它少了个括号,即人家都是 f(x) 它是 f x,这写法倒更像shell脚本了。其实它这个写法,颇有妙处的。

我们考虑一个多个自变量的函数,比如说

f x y = x + y

然后我们写 f 1 2 结果当然就是3,这个没啥,那如果我们写 f 1,它是啥意思呢?

我们不妨盲猜一下,我觉得它的含义是:

f 1 y = 1 + y

注意这里,f 1我们看做一个整体的话,那么它就应该是1 + y
假设我们再定义一个g

g = f 1

那我觉得,这就等价于

g y = 1 + y

那你觉得对不对呢?实验表明,对的。

怎么理解这件事?我们可以这么理解 f 1 这个东西:把f这个函数的第一个参数x的值绑定成1,然后计算这个函数,显然 x + y 就变成了 1 + y。

因此我们可以把f这个函数,不是理解为两个数字映射到一个数字,而是理解为把一个数字映射到一个函数,而后者这个函数,是把一个数字映射到一个数字。

这么说有那么一点绕,咱们看 f 1 2 这个调用,我们不要看做一个整体,而是看做:

(f 1) 2

即,先是f 1,然后得到的结果(还是个函数)再作用到2上面。
这就相当于

g = f 1
g 2

所以,咱们可以把所有的Haskell函数都看做只有一个参数的函数,只是这个函数的结果,可能还是个函数,实际上,Haskell就是这么理解的。比如我们看f的类型:

ghci> :t f
f :: Num a => a -> a -> a

这个意思是说,有个类型a,这个a是数字(Num),然后这个函数,是把类型 a 映射到 a 映射到 a。

err。它为啥不说是 (a, a) -> a 呢?

咱们仔细瞅瞅这个 a -> a -> a 注意后面这个 a -> a 是一个函数,把一个数映射到另一个数。所以前面的映射,就是把a映射成一个a->a的函数。

有点绕口令的感觉,不过你仔细琢磨清楚了,一定会感叹一句,如此妙哉。

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

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

相关文章

20222312 2024-2025-1 《网络与系统攻防技术》实验六实验报告

1.实验内容及要求 本实践目标是掌握metasploit的用法。 指导书参考Rapid7官网的指导教程。 https://docs.rapid7.com/metasploit/metasploitable-2-exploitability-guide/ 下载官方靶机Metasploitable2,完成下面实验内容。 (1)前期渗透 ①主机发现(可用Aux中的arp_sweep,s…

解决 PbootCMS 网站转移后无法打开报错提示“No input file specified”的问题

确保所有文件路径正确无误。检查 index.php 文件确保 index.php 文件存在于网站根目录中,并且路径正确。检查其他配置文件确保 config.php 和其他配置文件路径正确。查看错误日志 查看服务器日志,获取更多详细的错误信息。PHP 错误日志通常位于 /var/log/php7.x-fpm.log 或 /…

织梦dedecms无法登录后台,提示用户名或密码错误怎么办

问题描述:无法登录后台,提示用户名或密码错误。 解决方案:检查用户名和密码:确保输入的用户名和密码正确。 清除Cookie:清除浏览器的Cookie,重新尝试登录。 检查数据库:确保数据库中的管理员账户信息正确。 检查文件权限:确保后台目录和文件的权限设置正确。 检查配置文…

网站数据库如何修改config.php,如何在网站配置文件中修改数据库连接信息

修改网站的数据库连接信息可以确保网站能够正确连接到数据库。以下是具体步骤:备份文件:在修改前,备份当前的config.php文件,确保数据安全。 使用FTP工具(如FileZilla)下载config.php文件到本地。编辑文件:使用代码编辑器(如Sublime Text、Visual Studio Code)打开con…

修改公司网站 插件,如何在公司网站后台安装和管理插件

插件可以扩展网站的功能,提升用户体验。以下是具体步骤:登录后台:打开浏览器,输入网站的后台地址,例如 http://yourdomain.com/admin。 输入管理员账号和密码,点击“登录”。进入插件管理:登录后,点击顶部菜单栏中的“插件”或“扩展”。 选择“插件管理”或“扩展管理…

网站首页修改标题描述,如何在网站后台或代码编辑器中修改首页标题和描述

修改首页标题和描述可以提升搜索引擎优化(SEO)。以下是修改首页标题和描述的步骤:登录网站后台:打开浏览器,输入网站的后台地址,例如 http://yourdomain.com/admin。 输入管理员账号和密码,点击“登录”。进入SEO设置:登录后,点击顶部菜单栏中的“SEO”或“设置”。 选…

腾讯在线文档去掉底色

现在在线文档在单位使用范围比较广泛,前两天领导给我一个问题,在线文档标黄了部分文字,需要保留格式的情况下把底色去掉。当时在领导电脑上没解决,后面自己弄了个文档来测试解决了。分享给需要的人一个参考。 觉得过程麻烦的直接看最后解决方法,前面是过程。 1、在线文档导…

《计算机基础与程序设计》第九周

作业信息 这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计) 这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业) 这个作业的目标 <写上具体方面> 作业正文 ... 本博客链接 教材学习内容总结 《计算…

Java面试之多线程并发篇(5)

前言 本来想着给自己放松一下,刷刷博客,突然被几道面试题难倒!常用的线程池有哪些?简述一下你对线程池的理解?Java程序是如何执行的?锁的优化机制了解吗?说说进程和线程的区别?似乎有点模糊了,那就大概看一下面试题吧。好记性不如烂键盘 *** 12万字的java面试题整理 *…

阿里面试:1000万级大表, 如何 加索引?

本文原文链接 文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 …

团队项目4——项目冲刺-6

每日站立式会议昨天已完成的工作: 杨睿:处理页面跳转、错误状态处理等内容 开发物业报修页面木萨江:对接住户相关接口巴音才次克:完善住户个人资料页面李佳聪:测试住户相关接口今天计划完成的工作: 杨睿:完成住户物业报修页面木萨江:完成住户车位查询页面巴音才次克:对…

Codeforces Round 987 (Div. 2) - 比赛总结

Preface 我是若只。 A. Penchick and Modern Monument 先吃三发罚时。 最优策略应当是把所有数都调成众数,然而我一开始就忙着往后面做,胡乱猜了个结论就 WA 了,又猜了一个又 WA 了,再猜了一个再 WA 了。点击查看代码 const int N=105; int n,a[N];int main() {int T; read…