zoukankan      html  css  js  c++  java
  • JavaScript中的模式匹配

    JavaScript中的模式匹配

    模式是用于转换输入数据的规则。 以将数据与一个或多个逻辑结构进行比较,将数据分解为各个构成部分,或以各种方式从数据中提取信息。

    安装

    JavaScript已经实现模式匹配解构功能,没有实现模式匹配过滤功能。用模式来控制程序流,可以编写更加声明性,更加模块化的代码,请安装structural-comparison以支持此功能。structural-comparison是一个函数库。包括函数式编程的常用函数。开源于GitHub,见xp44mm/structural-comparison仓库。

    npm i structural-comparison
    

    用法:

    import { match } from 'structural-comparison'
    
    match: (pattern:string) -> (input:any) -> boolean
    

    match是一个柯里函数,模式参数是一个字符串,输入参数可以是任何值,当匹配成功返回真,否则返回假。

    示例

    模式匹配分为字面量模式,类型测试模式,标识符模式,数组模式,对象模式,OR模式,以及它们的组合嵌套模式。

    字面量模式

    测试原子值。模式是基元值字面量,支持JSON中的所有字面量。包括null,布尔值,数字值,字符串值。字符串是JSON的双引号格式。不支持undefinedNaNInfinity等非JSON值。

    test("value NULL", () => {
        let y = match('null')
        expect(y(null)).toEqual(true)
        expect(y(3)).toEqual(false)
    })
    

    这里y函数等价于:

    let y = input => input === null
    

    输入对象相等(===)于模式。

    其他字面量模式的示例:

    test("value boolean", () => {
        let y = match('false')
        let v = y(false)
        let w = y(3)
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("value number", () => {
        let y = match('123')
        let v = y(123)
        let w = y(3)
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("value quote", () => {
        let y = match('""')
        let v = y('')
        let w = y(3)
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    

    类型模式

    测试数据的数据类型。模式是数据类型的名称,不带双引号。包括boolean,string,number,function

    test("value TYPE", () => {
        let y = match('number')
        let v = y(5)
        let w = y(true)
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    

    这里y函数等价于:

    let y = input => typeof input === 'number'
    

    输入对象typeof值等于模式中的数据类型名称。

    标识符模式

    是一个合法的JavaScript标识符,除了标识符不包括$字符,但是不能是类型名称。模式中的标识符和类型名都是区分大小写的,这和JavaScript语法一致。标识符模式始终成功匹配任何一个值。

    test("value ID", () => {
        let y = match('x')
        let v = y(5)
        let w = y({})
        expect(v).toEqual(true)
        expect(w).toEqual(true)
    })
    

    通配模式虽然是标识符,但实际上,是一个弃元(discard),弃元表示一个我们完全用不上的数值。仅用于占位。相同的名称不会引起名称冲突。

    数组模式

    匹配一个数组。数组匹配根据数组元素长度分为长度严格匹配,和最短长度匹配。严格匹配示例:

    test("value array", () => {
        let y = match('[]')
        let v = y([])
        let w = y({})
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("array elements", () => {
        let input = '[1]'
        let y = match(input)
        let v = y([1])
        let w = y([{ x: 0 }])
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("elements elements value", () => {
        let input = '[1, 2]'
        let y = match(input)
        let v = y([1, 2])
        let w = y([null, 1])
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    

    如果有省略号表示可以匹配任何更多的数组元素。最短长度匹配:

    test("array ELLIPSIS", () => {
        let input = '[...]'
        let y = match(input)
        let v = y([1])
        let w = y({})
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    test("array elements ELLIPSIS", () => {
        let input = '[null, ...]'
        let y = match(input)
        let v = y([null])
        let w = y([null, 0])
        let p = y([0])
    
        expect(v).toEqual(true)
        expect(w).toEqual(true)
        expect(p).toEqual(false)
    })
    test("array ELLIPSIS elements", () => {
        let input = '[ ..., null]'
        let y = match(input)
        let v = y([null])
        let w = y([0, null])
        let p = y([null, 0])
    
        expect(v).toEqual(true)
        expect(w).toEqual(true)
        expect(p).toEqual(false)
    })
    
    test("array elements ELLIPSIS elements", () => {
        let y = match('[1,2,...,4,5]')
        let v = y([1,2,3,4,5])
        let w = y([1,2,3])
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    

    数组语法不支持洞(连续逗号),不支持尾逗号。不支持迭代器。

    数组模式大致编译成如下:

    let y = input => Array.isArray(input) && every elements matched
    

    对象模式

    匹配一个对象。如果有省略号表示对象可以有任何更多的属性。只检测自有属性(Object.keys),忽略原型中的属性。对象语法支持特殊标识属性,快捷属性,属性不支持尾逗号。

    test("value object", () => {
        let input = '{}'
        let y = match(input)
        let v = y({})
        let w = y({ x: 0 })
        
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("object ELLIPSIS", () => {
        let input = '{...}'
        let y = match(input)
        let v = y({})
        let w = y({ x: 0 })
        let p = y([])
    
        expect(v).toEqual(true)
        expect(w).toEqual(true)
        expect(p).toEqual(false)
    
    })
    
    test("object properties", () => {
        let input = '{x}'
        let y = match(input)
        let v = y({ x: 0 })
        let w = y([null, 1])
        
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("object properties ELLIPSIS", () => {
        let input = '{x,...}'
        let y = match(input)
        let v = y({ x: 0, y: 1 })
        let w = y({})
        
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    test("properties properties prop", () => {
        let input = '{x,y}'
        let y = match(input)
        let v = y({ x: 0, y: 1 })
        let w = y({})
        
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    
    test("prop key value", () => {
        let input = '{x:null}'
        let y = match(input)
        let v = y({ x: null })
        let w = y([null, 1])
        
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    
    })
    
    test("key QUOTE", () => {
        let input = '{"1":null}'
        let y = match(input)
        let v = y({ '1': null })
        let w = y([null, 1])
        
        expect(v).toEqual(true)
        expect(w).toEqual(false)
    })
    

    对象模式编译成:

    let y = obj => typeof obj === 'object' && obj && !Array.isArray(obj) && every props matched
    

    OR模式

    或模式,用一个竖杠符号连接两个模式,两个模式中任何一个模式成功即整体匹配成功。

    test("OR", () => {
        let y = match('string|{...}')
        let a = y('')
        let b = y({})
        let c = y([])
        let d = y(x => x)
        expect(a).toEqual(true)
        expect(b).toEqual(true)
        expect(c).toEqual(false)
        expect(d).toEqual(false)
    })
    

    OR模式编译成:

    let y = obj => pat1 matched || pat2 matched
    

    嵌套模式。匹配任意深度数据结构。

    memoize解析的结果

    上面的代码示例都用match(pattern)来缓存,但是实际上,我们常用if else条件选择语句来和模式匹配连用。

        let y = x => {
            if (match('[...]')(x)) console.log('[]')
            else if (match('{...}')(x)) console.log('{}')
            else if (match('string')(x)) console.log('string')
        }
        y([1]) // print []
    

    这个是没有缓存的程序,每次调用y函数都会重新解析模式,对性能造成负面冲击。所以,我们需要缓存。

        let arr = match('[...]')
        let obj = match('{...}')
        let str = match('string')
        let y = x => {
            if (arr(x)) console.log('[]')
            else if (obj(x)) console.log('{}')
            else if (str(x)) console.log('string')
        }
        y([1]) // print []
    

    上面程序成功解决了性能问题,避免重复解析,但是引入中间变量导致代码复杂难懂。我们使用另一种解决方案。

    import { match, cond } from 'structural-comparison'
    
    let y = cond([
            [match('[...]'), x => { console.log('[]') }],
            [match('{...}'), x => { console.log('{}') }],
            [match('string'), x => { console.log('string') }],
            x => {
                console.log('no matched!')
            }
        ])
    
        y([1]) // print []
        y({})  // print {}
        y(1)   // print no matched!
    

    cond函数是一个返回函数的组合子,用来模拟if else语句。它接受一个数组,数组的每个元素代表条件语句的一个分支。分支分为两种形式,第一种是断言函数,和行为函数组成的数组,当断言为真时,执行并返回行为,断言为假时跳过行为函数,执行下一分支。第二种是一个函数,当函数为真时,返回函数的返回值,当函数为假时,丢弃函数返回值,执行下一分支。cond函数依次执行每个分支,返回第一个为真的分支结果为整体的结果。忽略其后所有分支。

  • 相关阅读:
    第五周总结
    10.24号进度报告
    10.23日进度报告
    10.22日进度报告
    10.21日进度报告
    10.20号进度总结
    10.19日进度总结
    第四周总结
    10.18日进度博客
    2020下第六周总结
  • 原文地址:https://www.cnblogs.com/cuishengli/p/javascript-pattern-matching.html
Copyright © 2011-2022 走看看