最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 写给程序员看的函数式对话 7 - Maybe 和模式匹配

    正文概述 掘金(方应杭)   2021-04-11   542

    学生:好久不见啊,今天又有时间来聊天啊

    方:嗯,今天想跟你聊聊 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 的做法都安全,而且老手喜欢。

    学生:原来学好编程要掌握这么多编程语言才行

    方:没错!

    未完待续……


    起源地下载网 » 写给程序员看的函数式对话 7 - Maybe 和模式匹配

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元