编程语言之旅:从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的函数。

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

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空