首页 > 其他分享 >从C到Haskell

从C到Haskell

时间:2024-11-17 12:41:02浏览次数:1  
标签:函数 一个 代码 nX Haskell 100

缘起

开篇之前先说说为啥开始学习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的函数。

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

标签:函数,一个,代码,nX,Haskell,100
From: https://www.cnblogs.com/junfeng-blogs/p/18550422/c_to_haskell

相关文章

  • 使用 Haskell 实现基础图像识别
    在计算机科学领域,图像识别是一项复杂且广泛应用的任务。虽然Haskell主要以其函数式编程风格著称,但它同样可以用于图像识别。本文将展示如何在Haskell中实现简单的图像处理和识别。Haskell的优势Haskell是一种纯函数式编程语言,具有强大的类型系统和不可变性。这些特性使得......
  • 图像处理的实现与应用(Haskell 版)
    图像处理在现代技术中扮演着重要的角色,广泛应用于计算机视觉、图像分析和机器学习等领域。本文将介绍一种简单的图像处理方法,主要包括灰度转换、去除边框、提取有效区域和图像分割,并提供相应的Haskell代码示例。灰度转换灰度转换是将彩色图像转换为灰度图像的技术,目的是减少图......
  • 使用 Haskell 实现图标点选验证码识别及分割
    图标点选验证码是一种常见的防止自动化脚本攻击的手段,用户需要根据提示点击特定的图标来通过验证。本文将介绍如何用Haskell编写图标点选验证码的识别及分割代码。环境准备首先,我们需要安装一些必要的依赖项。在Haskell中,使用cabal或stack来管理项目和依赖库。为了处理......
  • Haskell爬虫:连接管理与HTTP请求性能
    爬虫技术作为数据抓取的重要手段,其效率和性能直接影响到数据获取的质量与速度。Haskell,作为一种纯函数式编程语言,以其强大的类型系统和并发处理能力,在构建高效爬虫方面展现出独特的优势。本文将探讨在Haskell中如何通过连接管理和HTTP请求优化来提升爬虫的性能。连接管理......
  • Haskell:面向对象OOP的实现
    Haskell作为一种纯函数式编程语言,并不直接支持传统面向对象编程(OOP)中的类、继承、多态等概念。然而,Haskell的强大类型系统和一些高级特性允许开发者以函数式的方式模拟OOP的某些方面。以下是一些Haskell中模拟OOP支持的示例:1.使用记录(Record)和类型类(Typeclass)模......
  • Haskell 的 自定义类型(data、type)
    在Haskell中,type和data关键字都用于定义新的数据类型,但它们有着不同的作用和语法。一、type关键字:作用:type关键字用于为已有类型创建别名,使得代码更易读和更具可读性。语法:其语法为typeNewType=ExistingType,其中NewType是新类型的名称,ExistingType是已有类......
  • 无涯教程-Haskell - Nested if-else 语句函数
    以下代码显示了如何在Haskell中使用嵌套的if-else语句-main=doletvar=26ifvar==0thenputStrLn"Numberiszero"elseifvar`rem`2==0thenputStrLn"NumberisEven"elseputStrLn"NumberisOdd"在上面的示例中......
  • 无涯教程-Haskell - Monads
    Monads只是一种带有某些附加函数的ApplicativeFunctor,它是一个Type类,它管理称为monadic规则的三个基本规则。所有这三个规则严格适用于Monad声明,如下所示-classMonadmwherereturn::a->ma(>>=)::ma->(a->mb)->mb(>>)::ma->mb->mb......
  • 无涯教程-Haskell - 输入&输出
    到目前为止,我们讨论的所有示例本质上都是静态的,在本章中,我们将学习与用户动态交流,我们将学习Haskell中使用的不同输入和输出技术。文件和流到目前为止,我们已经对程序本身中的所有输入进行了硬编码,我们一直在从静态变量获取输入,现在,让我们学习如何从外部文件读取和写入。让我们......
  • 无涯教程-Haskell - 函数组合
    功能组合是将一个功能的输出用作另一个功能的输入的过程,如果我们学习组成背后的数学会更好,在数学中,组成由f{g(x)}表示,其中g()是一个函数,其输出用作输入另一个功能,即f()。看下面的示例代码。在这里,我们使用函数组合来计算输入数字是偶数还是奇数。eveno::Int->Boolnoto......