学生:好久不见啊,今天又有时间来聊天啊
方:嗯,今天想跟你聊聊 Maybe 和模式匹配
直接上 TypeScript 代码:
const addMark = (whatever?: string) => whatever + '!'
addMark('Frank')
// 输出 Frank!
addMark()
// 输出 undefined!
最后输出的 undefined! 并不是我们想要的输出,一般你会怎么解决这样的问题?
学生:「判空」呗
方:没错,代码差不多是这样:
const addMark = (whatever?: string) => {
if(whatever !== undefined){
return whatever + '!'
} else {
return '!'
}
}
addMark()
// 输出 !
我问问你,现在 whatever 的类型是什么?
学生:string 呀
方:undefined 也是 string 吗?
学生:哦,我懂你意思了,whatever 的类型是 string | undefined
方:现在我给你介绍另一种思路,我们可以用 Maybe<string>
表示 whatever 的类型
学生:听不懂,代码怎么写
方:代码:
type Just<X> = { _type: 'Just', value: X }
type Nothing = { _type: 'Nothing' }
type Maybe<X> = Nothing | Just<X>
const createMaybe =
<T>(value:T): Maybe<T> =>
value === undefined ? {_type: 'Nothing'} : {_type: 'Just', value}
const addMark = (whatever: Maybe<string>) => {
if(whatever._type === 'Just' ){
return whatever.value + '!'
} else if(whatever.type === 'Nothing') {
return '!'
}
}
const readStringFromFile = ()=>{
return createMaybe<string>('hi')
}
const fileContent = readStringFromFile()
console.log(addMark(fileContent))
学生:确实是没有 undefined 和 null 了,但是你还是要判断 whatever._type 是 'Just' 还是 'Nothing' 不是吗?
方:是的,这是 JS 的表达能力有限所致,如果用 Haskell 写,配合模式匹配,代码就相当简洁了:
-- [Char] 就是 String
readStringFromFile :: [Char] -> Maybe [Char]
readStringFromFile path = Just "hi"
-- 文件可能不存在,返回空,这里我写死返回 "hi"
addMark :: Maybe [Char] -> [Char]
addMark (Just str) = str ++ "!"
addMark Nothing = "!"
main :: IO ()
main = do
print $ addMark $ readStringFromFile "./1.txt"
-- 输出 "hi!"
你看,没有 null / undefined,也没有 if else。
学生:模式匹配是什么?
方:其实很简单,我们只看 addMark
addMark :: Maybe [Char] -> [Char]
-- addMark 的参数类型是 Maybe [Char]
-- Maybe [Char] 只有两种情况:Just [Char] 和 Nothing
-- 如果是 Just [Char] 就给 str 后面加上感叹号
addMark (Just str) = str ++ "!"
-- 如果是 Nothing 就直接返回感叹号
addMark Nothing = "!"
学生:看起来跟 switch ... case 差不多啊
方:不一样,switch ... case 是对具体的「值」做比较,模式匹配则是一种「形式上」的匹配,更抽象一些。
接下来我们来练习一下模式匹配,这是斐波那契:
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
-- 这个版本极慢,有待优化
这是快排:
qs :: [Int] -> [Int]
qs [] = []
qs (first:rest) =
qs (filter (<= first) rest)
++ [first]
++ qs (filter (> first) rest)
你可以明显地看到,有了模式匹配,几乎就不用再写 if else 了。
学生:还挺方便,那 JS 为什么不引入模式匹配呢?
方:JS 也想引入,不过还在讨论阶段,这里有一个提案,具体代码长这样:
const res = await fetch(jsonService)
case (res) {
when {status: 200, headers: {'Content-Length': s}} ->
console.log(`size is ${s}`),
when {status: 404} ->
console.log('JSON not found'),
when {status} if (status >= 400) -> {
throw new RequestError(res)
},
}
case ... when ... 这样的代码就是模式匹配。
学生:模式匹配我大概了解了,就是高级版的 switch ... case。但是这个 Maybe 我还是不懂
方:在 Haskell 里,Maybe 的定义是
data Maybe a = Nothing | Just a -- 其中 a 可以是 Int / [Char] 等
作为参考,你可以看看 Haskell 里 Bool 的定义
data Bool = True | False
你不用在意关键字 data 是什么意思,你只需要用代入法即可,即
Maybe Int = Nothing | Just Int
Maybe [Char] = Nothing | Maybe [Char]
学生:那 Just "hi" 中的 Just 是什么?函数?还是类?
方:都不是,Just 就如同 True 或 False 一样,是特殊的值。Just "hi" 是一个整体,它不等于 "hi",其主要作用就是用来做模式匹配。
学生:那怎么从 Just "hi" 里取出 "hi" 呢?
方:你可以写一个 getValue
getValue :: Maybe [Char] -> [Char]
getValue (Just x) = x
getValue Nothing = error "无法读取值"
main = do
print $ getValue $ Just "hi" -- 输出 "hi"
但这是一种非常不推荐的做法。
学生:那推荐做法是?
方:推荐「先不要把值从 Maybe 里取出来」,在真正需要用到值的时候用模式匹配即可:
main = do
let maybe = getContentFromFile "./1.txt"
case maybe of
Just x -> print $ "result: " ++ x
Nothing -> print $ "we got nothing"
学生:我不太懂,我先取出来,得到一个 string,不是更方便吗?
方:JS 里确实是这样的,但是 Haskell 是一个支持惰性求值的语言。JS 不支持惰性求值还真不好解释,我举个另外的例子吧,如果 getContentFromFile 是异步操作,你怎么取出值?虽然你取不出值,但是你可以先把后续操作先写上去。
学生:你让我想到了 Promise
方:没错,promise 的值可能要 3 秒钟后返回,你不可能在那傻等 3 秒,不如趁这个时间把后续操作先写到 then 里。
let promise = readFilePromise("./1.txt")
promise.then(
x => console.log("result: " + x),
error => console.log("we go nothing")
)
有没有发现上面两段代码迷之相似?
学生:我怎么感觉,Maybe 就是同步的 Promise?Maybe<string>
表示可能有 string 也可能为空,Promise<User>
表示可能有 User 也可能为空。
方:有那么点意思,后面我们会发现它们的共通之处。
学生:JS 有了空,是不是就没有必要有 Maybe 类型了?
方:没错,就如同我们之前讲的「闭包和对象」一样,它们是殊途同归的,一门语言
- 要么像 JS 那样,设 whatever 的类型为 string | undefined,你写 whatever.split('') 的时候不报错,运行
- 要么像 TS 那样,设 whatever 的类型为 string | undefined,但是 whatever.split('') 报错,要求你先判空
- 要么像 Haskell 那样,whatever 的类型为 Maybe [Char],也就是 Just [Char] | Nothing,你无法直接通过 whatever 调用 string 的 API,除非你用模式匹配拿到 string 值
三种方案都可以达到相同的目的,其中 JS 的做法最不安全,但新手最喜欢。TS 和 Haskell 的做法都安全,而且老手喜欢。
学生:原来学好编程要掌握这么多编程语言才行
方:没错!
未完待续……
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!