

写给程序员看的函数式对话 7 - Maybe 和模式匹配
source link: https://zhuanlan.zhihu.com/p/362898971
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

写给程序员看的函数式对话 7 - Maybe 和模式匹配
学生:好久不见啊,今天又有时间来聊天啊
方:嗯,今天想跟你聊聊 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 的做法都安全,而且老手喜欢。
学生:原来学好编程要掌握这么多编程语言才行
方:没错!
未完待续……
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK