学生:好久不见啊,今天又有时间来聊天啊
方:嗯,今天想跟你聊聊 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 的做法最不安全,但新手最喜欢。TS 和 Haskell 的做法都安全,而且老手喜欢。
学生:原来学好编程要掌握这么多编程语言才行
方:没错!
未完待续……
饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com