Skip to content

Latest commit

 

History

History
264 lines (170 loc) · 4.71 KB

File metadata and controls

264 lines (170 loc) · 4.71 KB

基础

初始代码

先观察初始代码

module Main where

import Prelude
import Effect (Effect)
import Effect.Console (log)

main :: Effect Unit
main = log "hello"

这里, 第一行是定义当前文件的模块, 下面三行import是引入其他文件定义的东西.

Prelude模块里有常用的类型和函数, 通常会将其全量引入.

Effect模块定义了关于副作用的相关类型和函数, 这里我们引入了Effect这个类型.

另外我们从Effect.Console模块引入了log函数, 这个函数能将信息输出到控制台上.

这个程序执行后, 会在控制台看到"hello".

Show

如果改成这样:

main :: Effect Unit
main = log 1

会报错, 因为log需要输入一个String, 而不是一个Int.

幸运的是有一个函数show可以将常见类型转换为Int, 类似于js里的toString, 但也有些不同, 之后我们会细说.

总之可以改成这样:

main :: Effect Unit
main = log (show 1)

等量代换

这个语言的任何地方都可以通过等量代换来重构, 例如, 上面的代码可以被写成:

x = "hello"

main :: Effect Unit
main = log x

也可以:

x = log "hello"

main :: Effect Unit
main = x

类型签名

可以在值的上面一行写类型签名, 例如上面的代码可以写成:

x :: String
x = "hello"

main :: Effect Unit
main = log x

类型和值

类型是集合, 值是里面的元素.

常见的类型有, Int, String, Boolean等.

例如Boolean类型, 里面有两个元素, true和false.

而String和Int都有无限个元素, 是无限集合.

定义值

定义值的形式是

名称 :: 类型
名称 =

基础类型

a :: Int
a = 1

b :: String
b = "abc"

c :: Boolean
c = true

d :: Number
d = 1.0

复合类型

常见的复合类型有两种, 数组和记录.

数组

数组是0个, 1个或多个同类型的值组成的有序集. 和常见的语言的数组类型没有区别.

arr :: Array Int
arr = [ 1, 2, 3 ]

arr2 :: Array String
arr2 = [ "1", "2", "3" ]

记录

记录是0个, 1个或多个键值对组成的无序集. 类似于常见的语言中的字典类型.

r1 :: { a :: Number, b :: String, c :: String }
r1 = { a: 1.0, b: "aaa", c: "bbb" }

他们可以互相嵌套

r3 :: { a :: Number, b :: Array ({ x :: String }) }
r3 = { a: 1.0, b: [ { x: "aaa" }, { x: "bbb" } ] }

函数

函数是从一个值到另一个值的映射关系, 同时, 函数也是一种值.

简单匹配

例如

ff :: Boolean -> Int
ff true = 1
ff false = 0

这定义了一个函数, 它的类型是Boolean -> Int, 即输入一个布尔值, 返回一个Int值.

它枚举了两种可能, 输入true时, 返回1, 输入false时, 返回0.

如果现在有

fx1 :: Int
fx1 = ff true

注意到fx1 = ff true, 而观察ff的定义可以看到ff true = 1, 因此fx1 = 1.

模式匹配

例如

f3 :: Int -> Int
f3 ax = ax + 1

现在有

fx3 :: Int
fx3 = f3 1

其中, f3 ax称为模式, 其中ax会匹配任何值.

f3 1而言, 可以被匹配成f3 ax, 其中ax = 1.

而对模式f3 ax而言, 等价于ax + 1, 因此f3 1 = 2.

不全匹配和通用匹配

例如

f5 :: Int -> Int
f5 0 = 1
f5 1 = 2

这样会报错, 因为f5的类型签名写, 输入一个Int类型的值, 返回一个Int类型的值.

但定义f5的过程中, 只定义了0和1的模式, 如果输入其他值就搞不成了.

函数必须覆盖所有可能的输入值.

可以改成

f5 :: Int -> Int
f5 0 = 1
f5 1 = 2
f5 _ = 0

其中, 下划线也是一种模式值, 类似上面的ax, 匹配任意值, 但不同的是, 下划线匹配的值不会被使用.

柯里化

例如

f6 :: Int -> Int -> Int
f6 0 bx = 0 + bx
f6 ax bx = ax + bx

这里f6有两个参数, 但我可以只传入一个参数来调用它.

fx7 :: Int -> Int
fx7 = f6 1

其实并不存在"两个参数的函数", 所有函数都只有一个参数, 所谓的"两个参数的函数"只是"输入一个参数, 返回一个函数的函数".

这种特性叫函数的柯里化.

实例

一个阶乘函数

module Main where

import Prelude
import Effect (Effect)
import Effect.Console (log)

jiechen :: Int -> Int
jiechen 0 = 1
jiechen n = n * (jiechen (n - 1))

main :: Effect Unit
main = log (show (jiechen 5)) -- 得到120