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函數依次執行每個分支,返回第一個為真的分支結果為整體的結果。忽略其后所有分支。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM