• 小白入门Haskell 语言


    Haskell 语言

    安装

    因为我是 Windows 系统,在这里下载一个 GHC for Win 后解压缩,将解压缩后的目录中的 bin 添加到环境变量的 Path 中,在命令行中输入 ghci 就可用交互式的了。

    其中 bin 目录下有一个 runhaskell.exe 文件,我们对一个后缀为 .hs 的文件应用这个可执行文件后,就能写下面的代码了,而不是在 ghci 中交互,虽然那样学习更方便。

    学习
    四则运算

    值得一提的是除法,我们可以写出如下 main 函数并打印结果,如

    main = do print (5 / 2)
    
    • 1

    结果为 2.5 而不是 Cpp 中的 2 ,如果我们需要欧几里得除法则可以这样写

    main = do print (5 `div` 2) -- div 5 2 也是一样的
    
    • 1

    结果为 2取模也不是通常的 % 符号而是

    main = do print (5 `mod` 2) -- mod 5 2 也是一样的
    
    • 1
    布尔值

    TrueFalse 表示,用 not 表示取反, && 表示逻辑与, || 表示逻辑或,如

    main = do
      let c = 10
      if not (c > 11)
        then putStr "c <= 10"
      else putStr "c > 11"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    需要注意括号的位置,基本上我们可以认为 not 是一个函数。

    比较两个数

    main = do
      let c = 10
      if 5 /= c
        then putStr "c not eqaul to 5"
      else putStr "c is 5"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意这里的不等于使用的是 a /= b 而不是 a != b ,而等于则为 a == b 与其他语言是类似的。

    一些基本函数
    main = do print (succ 8) -- 9
    
    • 1
    main = do print (min 10 11) -- 10
    
    • 1
    main = do print (max 10 11) -- 11
    
    • 1

    函数的一些顺序有下面这个例子

    main = do print (succ 9 + max 5 4 + 1)
    
    • 1

    这等价于

    main = do print ((succ 9) + (max 5 4) + 1)
    
    • 1
    编写自己的函数
    doubleMe x = x + x
    main = do
      print (doubleMe 9) -- 18
    
    • 1
    • 2
    • 3
    squareMe x = x * x
    main = do
      print (squareMe 9) -- 81
    
    • 1
    • 2
    • 3

    当然也可以用小数调用这些函数如

    doubleMe x = x + x
    main = do
      print (doubleMe 9.1) -- 18.2
    
    • 1
    • 2
    • 3

    也就是说我们写的函数实际上是一个泛型的函数!这很棒,对于限制类型后面会提到。

    多参数的函数

    addxy x y = x + y
    main = do
      print (addxy 7 8) -- 15
    
    • 1
    • 2
    • 3

    带有 if 的函数

    plusOne x = if x == 0 then x + 1 else -1
    main = do
      print (plusOne 1)
      print (plusOne 0)
    
    • 1
    • 2
    • 3
    • 4

    当然我们可以调整一些缩进写成

    plusOne x =
      if x == 0
        then x + 1
        else -1
    
    main = do
      print (plusOne 1)
      print (plusOne 0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样使得代码更具有可读性。

    list

    我不清楚是否应该称呼其为数组,我们可以写出这样的代码

    main = do
      let c = [1,2,3,4]
      print c -- [1,2,3,4]
    
    • 1
    • 2
    • 3

    而我们常用的字符串如 "abc" 实际上为 ['a','b','c'] 的语法糖,这与在 C 语言中有点类似,也就是说

    main = do
      print ("abc" == ['a','b','c']) -- True
    
    • 1
    • 2

    是毫无疑问的。

    对于 list 拼接也显得很自然,有

    c = [1,2,3] ++ [2,3,4]
    main = do
      print c -- [1,2,3,2,3,4]
    
    • 1
    • 2
    • 3

    也有这样的拼接方法如

    a = 10
    b = [1,2,3]
    main = do
      print (a : b) -- [10,1,2,3]
    
    • 1
    • 2
    • 3
    • 4

    下标访问,在 C 语言中我们常常会这样写

    #include 
    int main(void) {
      int a[10];
      printf("%d", a[1]); // 未初始化,值可能为随机
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    而在 Haskell 中则是

    a = "abcdefg"
    main = do
      putChar (a !! 2) -- c
    
    • 1
    • 2
    • 3

    我们发现其索引开始也是 0 ,这与一般的习惯很符合。

    值得注意的是, list 之间也可以通过 <>>=<= 比较,结果好像为字典序(但一般很少用到)。

    head 函数只取首个元素

    c = [1,2,3,4,5]
    main = do
      print (head c) -- 1
    
    • 1
    • 2
    • 3

    tail 函数为除了首个元素的其余元素的 list

    c = [1,2,3,4,5]
    main = do
      print (tail c) -- [2,3,4,5]
    
    • 1
    • 2
    • 3

    last 函数为最后一个元素

    c = [1,2,3,4,5]
    main = do
      print (last c) -- 5
    
    • 1
    • 2
    • 3

    init 函数为除了最后一个元素的其余元素的 list

    c = [1,2,3,4,5]
    main = do
      print (last c) -- [1,2,3,4]
    
    • 1
    • 2
    • 3

    注意, head 不能应用于空的 list ,其余函数我没有测试。

    length 函数返回 list 的长度

    main = do
      print (length [1,2,3]) -- 3
      print (length []) -- 0
    
    • 1
    • 2
    • 3

    null 函数返回一个布尔值表示 list 是否为空

    main = do
      print (null []) -- True
      print (null [1]) -- False
    
    • 1
    • 2
    • 3

    reverse 函数返回一个翻转的 list

    c = [1,2,3]
    main = do
      print (reverse c) -- [3,2,1]
    
    • 1
    • 2
    • 3

    上面所说的函数全都是一个参数的函数,且参数都为 list ,下面的函数为两个参数,具体看示例为

    take 函数表示截取前几个元素组成新的 list

    c = [1,2,3,4,5]
    main = do
      print (take 3 c) -- [1,2,3]
      print (take 0 c) -- []
      print (take 8 c) -- [1,2,3,4,5]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    drop 函数表示丢弃前几个元素组成新的 list

    c = [1,2,3,4,5]
    main = do
      print (drop 3 c) -- [4,5]
      print (drop 0 c) -- [1,2,3,4,5]
      print (drop 100 c) -- []
    
    • 1
    • 2
    • 3
    • 4
    • 5

    maximum 函数与 minimum 函数想必不必多说

    c = [1,2,3,4,5]
    main = do
      print (maximum c) -- 5
      print (minimum c) -- 1
    
    • 1
    • 2
    • 3
    • 4

    sum 函数返回和

    c = [1,2,3]
    main = do
      print (sum c) -- 6
    
    • 1
    • 2
    • 3

    product 函数返回乘积

    c = [1,2,3]
    main = do
      print (product c) -- 6
    
    • 1
    • 2
    • 3

    elem 函数告诉我们这个元素是否在 list 中

    c = [1,2,3]
    main = do
      print (1 `elem` c) -- True
      print (-1 `elem` c) -- False
    
    • 1
    • 2
    • 3
    • 4

    通常用一个中缀表达式来写,当然也可以写作 elem 1 c ,因为更自然,与之前的 divmod 是类似的。

    范围的 list 可以这样写

    main = do
      print [1..5] -- [1,2,3,4,5]
      print [2,4..10] -- [2,4,6,8,10]
      print [3,6..20] -- [3,6,9,12,15,18]
    
    • 1
    • 2
    • 3
    • 4

    但是不能写 [5..1] 而必须写 [5,4..1] ,我们可以理解为等差数列前两项以及最后一项,但小数的时候不同

    main = do
      print [0.1,0.3 .. 1] -- [0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
    
    • 1
    • 2

    cycle 函数可以将一个东西无限制的拼接如

    main = do
      print (take 10 (cycle [1,2,3])) -- [1,2,3,1,2,3,1,2,3,1]
    
    • 1
    • 2

    于是我们只能用 take 函数将其取出部分。

    repeat 函数则有

    main = do
      print (take 10 (repeat 5)) -- [5,5,5,5,5,5,5,5,5,5]
    
    • 1
    • 2

    也有更方便的函数 replicate 可以做到

    main = do
      print (replicate 3 10) -- [10,10,10]
    
    • 1
    • 2

    接下来是一个很厉害的工具,可以制作我们想要的 list 形态,例如我想要 { x ∣ 0 ≤ x ≤ 20 , x   m o d   3 = 1 , x ∈ Z } \{x\mid 0\leq x\leq 20,x\bmod 3=1,x\in\mathbb{Z}\} {x0x20,xmod3=1,xZ} 的 list 可以写

    main = do
      print [x | x <- [0..20], x `mod` 3 == 1]
    
    • 1
    • 2

    这甚至与数学中很类似。

    我们也可以写函数接收一个 list 返回出想要的 list 如

    weWant xs = [x | x <- xs, odd x]
    main = do
      print (weWant [0,1,7,9]) -- [1,7,9]
    
    • 1
    • 2
    • 3

    上面用到了函数 odd 同样的也有 even

    不止如此,我们也可以写出

    prodEachElem xs ys = [x * y | x <- xs, y <- ys]
    main = do
      print (prodEachElem [1,2,3] [4,5,6]) -- [4,10,18]
    
    • 1
    • 2
    • 3

    用数学表达则为 x = { 1 , 2 , 3 } , y = { 4 , 5 , 6 } , z = { a b ∣ a ∈ x , b ∈ y } x=\{1,2,3\},y=\{4,5,6\},z=\{ab\mid a\in x,b\in y\} x={1,2,3},y={4,5,6},z={abax,by} 很自然。

    回顾之前的函数 length 我们可以用 sum 写一个自己的版本为

    length' x = sum [1 | _ <- x]
    main = do
      print (length' []) -- 0
      print (length' [1,2,3]) -- 3
    
    • 1
    • 2
    • 3
    • 4

    他可以正常的工作,其中下划线 _ 代表我们并不关心这个是什么。

    tuple

    中文一般为元组。元组一般为 (a,b) 这样的形式。

    fst 函数返回元组中的首个元素,而 snd 返回第二个。

    main = do
      print (fst (1,"WWW")) -- 1
      print (snd (2,"WWWA")) -- "WWWA"
    
    • 1
    • 2
    • 3

    有一个很酷的函数为 zip 可以制作元组如

    main = do
      print (zip [1,2,3] ["A","B","C"]) -- [(1,"A"),(2,"B"),(3,"C")]
      print (zip [1..3] ['A','B','C']) -- [(1,'A'),(2,'B'),(3,'C')]
      print (zip [1..] ['A','B']) -- [(1,'A'),(2,'B')]
    
    • 1
    • 2
    • 3
    • 4

    可以看到是以 length 较短的标准制作的,这样的话写出 [1..] 这样看似无穷多的 list 也变得合理了(惰性?)。

    当然,元组也远远不止两个元素,也可以写出

    d = [(a,b,c) | a <- [1,3..10], b <- [2,4..7], c <- [7,11..18]]
    main = do
      print d
    
    • 1
    • 2
    • 3

    注意这里并不是 zip 函数,所以不要写无穷无尽的,我们可以将这里视为三重的循环体。刚刚出了事故我的电脑死机了,看来这个编译器会一丝不苟的执行下去呢,并没有检测是否会造成死循环。

    类型

    参考资料

    [1] http://learnyouahaskell.com/chapters

  • 相关阅读:
    如何修改glog由进程生成日志为按天输出日志
    元宇宙简介
    mysql 数据库链接状态确认实验
    阿里云ECS服务器配置怎么选?
    Vite+React+Electron开发入门,10分钟搭建本地环境并打包
    【美团秋招笔试】美团第一次笔试 2022-8-20
    Nginx基本介绍
    C语言统计成绩
    NumPy 数组应用初探
    对强缓存和协商缓存的理解
  • 原文地址:https://blog.csdn.net/hebtu666/article/details/126587376