38 |
39 | match (res) {'{'}
40 |
41 | {' '}when (
42 |
43 | {' '}
44 | {'{ status: 200, body, '}
45 | ...
46 | {'rest }'}
47 |
48 |
49 | {' '}
50 | ):
51 |
52 |
53 | {' '}
54 | handleData(body, rest)
55 |
56 |
57 | {' '}when (
58 |
59 | {' '}
60 | {'{ status, destination: '}
61 | url
62 | {' }'}
63 |
64 |
65 | {' '}
66 | {') if (300 <= status && status < 400):'}
67 |
68 | {' '}handleRedirect(url)
69 |
70 | {' '}when (
71 |
72 | {' '}
73 | {'{ status: 500 }'}
74 |
75 |
76 | {' '}
77 | ) if (!this.hasRetried):
78 |
79 |
80 | {' '}
81 | do
82 | {'{'}
83 |
84 | {' '}retry(req)
85 | {' '}this.hasRetried = true
86 |
87 | {' '}
88 | {'}'}
89 |
90 |
91 |
92 |
93 | {' '}
94 | default: throwSomething()
95 |
96 |
97 | {'}'}
98 |
99 |
100 |
101 | match (res)
(
102 |
103 |
104 | {' '}
105 | when (
106 |
107 |
108 | {' '}
109 | {'{ status: 200, body'}
110 |
: defined
111 | {', rest }'}
112 |
,
113 |
114 |
115 | {' '}
116 |
({'{ body, rest }) =>'}
117 |
118 |
119 | {' '}
120 | handleData(body, rest)
121 |
122 |
123 | {' '}
124 |
),
125 |
126 |
127 | {' '}
128 | when (
129 |
130 |
131 | {' '}
132 | {'{ status'}
133 |
: between(300, 400), destination:
defined
134 | {' }'}
135 |
,
136 |
137 |
138 | {' '}
139 |
{'({ destination: url }) =>'}
140 |
141 |
142 | {' '}
143 | handleRedirect(url)
144 |
145 |
146 | {' '}
147 |
),
148 |
149 |
150 | {' '}
151 | when (
152 |
153 |
154 | {' '}
155 | {'{ status: 500 }'}
156 |
,
157 |
158 |
159 | {' '}
160 |
{'() =>'} !this.hasRetried
,
161 |
162 |
163 | {' '}
164 |
{'() =>'}
165 | {'{'}
166 |
167 |
168 | {' '}
169 | retry(req)
170 |
171 |
172 | {' '}
173 | this.hasRetried = true
174 |
175 |
176 | {' '}
177 | {'}'}
178 |
179 |
180 | {' '}
181 |
),
182 |
183 |
184 | {' '}
185 |
{'otherwise (() =>'} throwSomething()
)
186 |
187 |
190 |
191 | )
192 |
193 | console.log(ReactDOMServer.renderToStaticMarkup(patcom))
194 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/conditional-jsx.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { ConditionalJsx } from './sample.js'
4 |
5 | function h(component, props, children) {
6 | return component({ ...props, children })
7 | }
8 | describe('tc39-proposal-pattern-matching', () => {
9 | describe('conditional jsx sample', () => {
10 | test('returns Loading component when loading field is present', () => {
11 | const Fetch = ({ children }) => {
12 | return children({ loading: true })
13 | }
14 | const Loading = () => 'Loading'
15 | const Error = ({ error }) => `Failed with ${error}`
16 | const Page = ({ data }) => `Page with ${data}`
17 | const Component = ConditionalJsx(
18 | h,
19 | 'some api url',
20 | Fetch,
21 | Loading,
22 | Error,
23 | Page
24 | )
25 | expect(h(Component)).toBe('Loading')
26 | })
27 |
28 | test('returns Error component when error field is present', () => {
29 | const Fetch = ({ children }) => {
30 | return children({ error: 'some error' })
31 | }
32 | global.console.err = jest.fn()
33 | const Loading = () => 'Loading'
34 | const Error = ({ error }) => `Failed with ${error}`
35 | const Page = ({ data }) => `Page with ${data}`
36 | const Component = ConditionalJsx(
37 | h,
38 | 'some api url',
39 | Fetch,
40 | Loading,
41 | Error,
42 | Page
43 | )
44 | expect(h(Component)).toBe('Failed with some error')
45 | expect(global.console.err).toHaveBeenNthCalledWith(
46 | 1,
47 | 'something bad happened'
48 | )
49 | delete global.console.err
50 | })
51 |
52 | test('returns Page component when data field is present', () => {
53 | const Fetch = ({ children }) => {
54 | return children({ data: 'some data' })
55 | }
56 | const Loading = () => 'Loading'
57 | const Error = ({ error }) => `Failed with ${error}`
58 | const Page = ({ data }) => `Page with ${data}`
59 | const Component = ConditionalJsx(
60 | h,
61 | 'some api url',
62 | Fetch,
63 | Loading,
64 | Error,
65 | Page
66 | )
67 | expect(h(Component)).toBe('Page with some data')
68 | })
69 |
70 | test('returns nothing otherwise', () => {
71 | const Fetch = ({ children }) => {
72 | return children({})
73 | }
74 | const Loading = () => 'Loading'
75 | const Error = ({ error }) => `Failed with ${error}`
76 | const Page = ({ data }) => `Page with ${data}`
77 | const Component = ConditionalJsx(
78 | h,
79 | 'some api url',
80 | Fetch,
81 | Loading,
82 | Error,
83 | Page
84 | )
85 | expect(h(Component)).toBe()
86 | })
87 | })
88 | })
89 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/custom-matcher-option.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { CustomMatcherOption, Option } from './sample.js'
4 |
5 | describe('tc39-proposal-pattern-matching', () => {
6 | describe('customer matcher sample', () => {
7 | test('call console.log with val on some result', () => {
8 | const log = jest.spyOn(global.console, 'log').mockImplementation()
9 | const matcher = CustomMatcherOption
10 | matcher(Option.Some('some value'))
11 | expect(log).toHaveBeenNthCalledWith(1, 'some value')
12 | })
13 |
14 | test('call console.log with none on none result', () => {
15 | const log = jest.spyOn(global.console, 'log').mockImplementation()
16 | const matcher = CustomMatcherOption
17 | matcher(Option.None())
18 | expect(log).toHaveBeenNthCalledWith(1, 'none')
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/concept-not-found/patcom/b0ce82e93e95d02123e7aff2bb1b7a1c4d8fd488/tc39-proposal-pattern-matching/diff.png
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/fetch-json-response.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { FetchJsonResponse } from './sample.js'
4 |
5 | describe('tc39-proposal-pattern-matching', () => {
6 | describe('fetch json response sample', () => {
7 | test('call console.log with content length on 200', async () => {
8 | const log = jest.spyOn(global.console, 'log').mockImplementation()
9 | async function fetch() {
10 | return {
11 | status: 200,
12 | headers: { 'Content-Length': 42 },
13 | }
14 | }
15 | await FetchJsonResponse(fetch)
16 | expect(log).toHaveBeenNthCalledWith(1, 'size is 42')
17 | })
18 |
19 | test('call console.log with not found on 404', async () => {
20 | const log = jest.spyOn(global.console, 'log').mockImplementation()
21 | async function fetch() {
22 | return {
23 | status: 404,
24 | }
25 | }
26 | await FetchJsonResponse(fetch)
27 | expect(log).toHaveBeenNthCalledWith(1, 'JSON not found')
28 | })
29 |
30 | test.each([{ status: 400 }, { status: 401 }, { status: 500 }])(
31 | 'throws if status is $status',
32 | ({ status }) => {
33 | async function fetch() {
34 | return {
35 | status,
36 | }
37 | }
38 | expect(FetchJsonResponse(fetch)).rejects.toThrow()
39 | }
40 | )
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/index.js:
--------------------------------------------------------------------------------
1 | import { ValueMatcher, asMatcher, unmatched } from '../index.js'
2 | import TimeJumpIterator from '../time-jump-iterator.js'
3 |
4 | export function cachedProperties(source) {
5 | const cache = {}
6 | return new Proxy(source, {
7 | get(target, prop) {
8 | if (!cache[prop]) {
9 | cache[prop] = source[prop]
10 | }
11 | return cache[prop]
12 | },
13 | })
14 | }
15 |
16 | export const cachingOneOf = (...matchables) =>
17 | ValueMatcher((value) => {
18 | const iteratorValue =
19 | typeof value !== 'string' &&
20 | !Array.isArray(value) &&
21 | value[Symbol.iterator]
22 | if (iteratorValue) {
23 | value = TimeJumpIterator(value)
24 | } else if (typeof value === 'object') {
25 | value = cachedProperties(value)
26 | }
27 | for (const matchable of matchables) {
28 | if (iteratorValue) {
29 | value.jump(0)
30 | }
31 | const matcher = asMatcher(matchable)
32 | const result = matcher(value)
33 | if (result.matched) {
34 | return result
35 | }
36 | }
37 | return unmatched
38 | })
39 |
40 | export const match =
41 | (value) =>
42 | (...clauses) => {
43 | const result = cachingOneOf(...clauses)(value)
44 | return result.value
45 | }
46 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/nil-pattern.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { NilPattern } from './sample.js'
4 |
5 | describe('tc39-proposal-pattern-matching', () => {
6 | describe('nil pattern sample', () => {
7 | test('call console.log 3rd value with two holes before it', async () => {
8 | const log = jest.spyOn(global.console, 'log').mockImplementation()
9 | NilPattern([, , 'some value'])
10 | expect(log).toHaveBeenNthCalledWith(1, 'some value')
11 | })
12 |
13 | test('call console.log 3rd value with filled holes', async () => {
14 | const log = jest.spyOn(global.console, 'log').mockImplementation()
15 | NilPattern(['filled', 'filled', 'some value'])
16 | expect(log).toHaveBeenNthCalledWith(1, 'some value')
17 | })
18 |
19 | test('does nothing with 3 holes', async () => {
20 | const log = jest.spyOn(global.console, 'log').mockImplementation()
21 | NilPattern([, ,])
22 | expect(log).not.toHaveBeenCalled()
23 | })
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/object-pattern-caching.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { ObjectPatternCaching } from './sample.js'
4 |
5 | describe('tc39-proposal-pattern-matching', () => {
6 | describe('object pattern caching sample', () => {
7 | test('matches number with single read to Math.random less than half', () => {
8 | const log = jest.spyOn(global.console, 'log').mockReturnValue()
9 | const random = jest.spyOn(global.Math, 'random').mockReturnValue(0.2)
10 | ObjectPatternCaching()
11 | expect(log).toHaveBeenNthCalledWith(1, 'Only matches half the time.')
12 | expect(random).toHaveBeenCalledTimes(1)
13 | })
14 |
15 | test('matches string with single read to Math.random greater than half', () => {
16 | const log = jest.spyOn(global.console, 'log').mockReturnValue()
17 | const random = jest.spyOn(global.Math, 'random').mockReturnValue(0.8)
18 | ObjectPatternCaching()
19 | expect(log).toHaveBeenNthCalledWith(
20 | 1,
21 | 'Guaranteed to match the other half of the time.'
22 | )
23 | expect(random).toHaveBeenCalledTimes(1)
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/regexp-groups.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { RegExpGroups } from './sample.js'
4 |
5 | describe('tc39-proposal-pattern-matching', () => {
6 | describe('regexp groups sample', () => {
7 | test('calls process with named groups on 1 + 2', () => {
8 | const process = jest.fn()
9 | const handleOtherwise = jest.fn()
10 | const matcher = RegExpGroups(process, handleOtherwise)
11 | const arithmeticStr = '1 + 2'
12 | matcher(arithmeticStr)
13 | expect(process).toHaveBeenNthCalledWith(1, '1', '2')
14 | expect(handleOtherwise).not.toHaveBeenCalled()
15 | })
16 |
17 | test('calls process with array match on 1 * 2', () => {
18 | const process = jest.fn()
19 | const handleOtherwise = jest.fn()
20 | const matcher = RegExpGroups(process, handleOtherwise)
21 | const arithmeticStr = '1 * 2'
22 | matcher(arithmeticStr)
23 | expect(process).toHaveBeenNthCalledWith(1, '1', '2')
24 | expect(handleOtherwise).not.toHaveBeenCalled()
25 | })
26 |
27 | test('calls handleOtherwise on 1 / 2', () => {
28 | const process = jest.fn()
29 | const handleOtherwise = jest.fn()
30 | const matcher = RegExpGroups(process, handleOtherwise)
31 | const arithmeticStr = '1 / 2'
32 | matcher(arithmeticStr)
33 | expect(handleOtherwise).toHaveBeenNthCalledWith(1)
34 | expect(process).not.toHaveBeenCalled()
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/res-handler.spec.js:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { ResHandler } from './sample.js'
4 |
5 | describe('tc39-proposal-pattern-matching', () => {
6 | describe('res handler sample', () => {
7 | test('handleData called on status 200', () => {
8 | const handleData = jest.fn()
9 | const handleRedirect = jest.fn()
10 | const retry = jest.fn()
11 | const throwSomething = jest.fn()
12 | const RetryableHandler = ResHandler(
13 | handleData,
14 | handleRedirect,
15 | retry,
16 | throwSomething
17 | )
18 | const retryableHandler = new RetryableHandler()
19 | const req = 'some request'
20 | const res = {
21 | status: 200,
22 | body: 'some body',
23 | otherField: 'other field',
24 | moreField: 'more field',
25 | }
26 | retryableHandler.handle(req, res)
27 | expect(handleData).toHaveBeenNthCalledWith(1, 'some body', {
28 | otherField: 'other field',
29 | moreField: 'more field',
30 | })
31 | expect(handleRedirect).not.toHaveBeenCalled()
32 | expect(retry).not.toHaveBeenCalled()
33 | expect(throwSomething).not.toHaveBeenCalled()
34 | })
35 |
36 | test.each([
37 | {
38 | status: 300,
39 | },
40 | {
41 | status: 301,
42 | },
43 | {
44 | status: 302,
45 | },
46 | {
47 | status: 399,
48 | },
49 | ])('handleRedirect called on status $status', ({ status }) => {
50 | const handleData = jest.fn()
51 | const handleRedirect = jest.fn()
52 | const retry = jest.fn()
53 | const throwSomething = jest.fn()
54 | const RetryableHandler = ResHandler(
55 | handleData,
56 | handleRedirect,
57 | retry,
58 | throwSomething
59 | )
60 | const retryableHandler = new RetryableHandler()
61 | const req = 'some request'
62 | const res = {
63 | status,
64 | destination: 'some url',
65 | }
66 | retryableHandler.handle(req, res)
67 | expect(handleRedirect).toHaveBeenNthCalledWith(1, 'some url')
68 | expect(handleData).not.toHaveBeenCalled()
69 | expect(retry).not.toHaveBeenCalled()
70 | expect(throwSomething).not.toHaveBeenCalled()
71 | })
72 |
73 | test('retry called on status 500 on the first attempt', () => {
74 | const handleData = jest.fn()
75 | const handleRedirect = jest.fn()
76 | const retry = jest.fn()
77 | const throwSomething = jest.fn()
78 | const RetryableHandler = ResHandler(
79 | handleData,
80 | handleRedirect,
81 | retry,
82 | throwSomething
83 | )
84 | const retryableHandler = new RetryableHandler()
85 | const req = 'some request'
86 | const res = {
87 | status: 500,
88 | }
89 | retryableHandler.handle(req, res)
90 | expect(retry).toHaveBeenNthCalledWith(1, 'some request')
91 | expect(handleData).not.toHaveBeenCalled()
92 | expect(handleRedirect).not.toHaveBeenCalled()
93 | expect(throwSomething).not.toHaveBeenCalled()
94 | })
95 |
96 | test('throwSomething called on consecutive status 500', () => {
97 | const handleData = jest.fn()
98 | const handleRedirect = jest.fn()
99 | const retry = jest.fn()
100 | const throwSomething = jest.fn()
101 | const RetryableHandler = ResHandler(
102 | handleData,
103 | handleRedirect,
104 | retry,
105 | throwSomething
106 | )
107 | const retryableHandler = new RetryableHandler()
108 | const req = 'some request'
109 | const res = {
110 | status: 500,
111 | }
112 | retryableHandler.handle(req, res)
113 | retryableHandler.handle(req, res)
114 | expect(retry).toHaveBeenCalledTimes(1)
115 | expect(throwSomething).toHaveBeenCalledTimes(1)
116 | expect(handleData).not.toHaveBeenCalled()
117 | expect(handleRedirect).not.toHaveBeenCalled()
118 | })
119 |
120 | test('throwSomething called on status 400', () => {
121 | const handleData = jest.fn()
122 | const handleRedirect = jest.fn()
123 | const retry = jest.fn()
124 | const throwSomething = jest.fn()
125 | const RetryableHandler = ResHandler(
126 | handleData,
127 | handleRedirect,
128 | retry,
129 | throwSomething
130 | )
131 | const retryableHandler = new RetryableHandler()
132 | const req = 'some request'
133 | const res = {
134 | status: 400,
135 | }
136 | retryableHandler.handle(req, res)
137 | expect(throwSomething).toHaveBeenCalledTimes(1)
138 | expect(handleData).not.toHaveBeenCalled()
139 | expect(retry).not.toHaveBeenCalled()
140 | expect(handleRedirect).not.toHaveBeenCalled()
141 | })
142 | })
143 | })
144 |
--------------------------------------------------------------------------------
/tc39-proposal-pattern-matching/sample.js:
--------------------------------------------------------------------------------
1 | import {
2 | matcher,
3 | ValueMatcher,
4 | matchNumber,
5 | matchBigInt,
6 | matchString,
7 | matchArray,
8 | greaterThan,
9 | greaterThanEquals,
10 | defined,
11 | rest,
12 | oneOf,
13 | allOf,
14 | matchProp,
15 | when,
16 | otherwise,
17 | between,
18 | any,
19 | } from '../index.js'
20 |
21 | import { match } from './index.js'
22 |
23 | export const AdventureCommand =
24 | (handleGoDir, handleTakeItem, handleOtherwise) => (command) =>
25 | match(command)(
26 | when(['go', oneOf('north', 'east', 'south', 'west')], ([, dir]) =>
27 | handleGoDir(dir)
28 | ),
29 | when(['take', allOf(/[a-z]+ ball/, matchProp('weight'))], ([, item]) =>
30 | handleTakeItem(item)
31 | ),
32 | otherwise(() => handleOtherwise())
33 | )
34 |
35 | export const ArrayLength =
36 | (handleEmpty, handleSinglePage, handleMultiplePages, handleOtherwise) =>
37 | (res) =>
38 | match(res)(
39 | when({}, () => handleEmpty()),
40 | when({ data: [defined] }, ({ data: [page] }) => handleSinglePage(page)),
41 | when({ data: [defined, rest] }, ({ data: [frontPage, pages] }) =>
42 | handleMultiplePages(frontPage, pages)
43 | ),
44 | otherwise(() => handleOtherwise())
45 | )
46 |
47 | function* integers(to) {
48 | for (var i = 1; i <= to; i++) yield i
49 | }
50 |
51 | export const ArrayPatternCaching = (n) => {
52 | const iterable = integers(n)
53 | match(iterable)(
54 | when([matchNumber()], ([a]) => console.log(`found one int: ${a}`)),
55 | // Matching a generator against an array pattern.
56 | // Obtain the iterator (which is just the generator itself),
57 | // then pull two items:
58 | // one to match against the `a` pattern (which succeeds),
59 | // the second to verify the iterator only has one item
60 | // (which fails).
61 | when([matchNumber(), matchNumber()], ([a, b]) =>
62 | console.log(`found two ints: ${a} and ${b}`)
63 | ),
64 | // Matching against an array pattern again.
65 | // The generator object has already been cached,
66 | // so we fetch the cached results.
67 | // We need three items in total;
68 | // two to check against the patterns,
69 | // and the third to verify the iterator has only two items.
70 | // Two are already in the cache,
71 | // so we’ll just pull one more (and fail the pattern).
72 | otherwise(() => console.log('more than two ints'))
73 | )
74 | console.log([...iterable])
75 | // logs [4, 5]
76 | // The match construct pulled three elements from the generator,
77 | // so there’s two leftover afterwards.
78 | }
79 |
80 | function asciiCI(str) {
81 | return {
82 | [matcher](matchable) {
83 | return {
84 | matched: str.toLowerCase() == matchable.toLowerCase(),
85 | }
86 | },
87 | }
88 | }
89 |
90 | export const AsciiCi = (cssProperty) =>
91 | match(cssProperty)(
92 | when({ name: asciiCI('color'), value: defined }, ({ value }) =>
93 | console.log('color: ' + value)
94 | )
95 | // matches if `name` is an ASCII case-insensitive match
96 | // for "color", so `{name:"COLOR", value:"red"} would match.
97 | )
98 |
99 | export const AsyncMatch = async (somethingThatRejects, matchable) =>
100 | match(await matchable)(
101 | when({ a: defined }, async ({ a }) => await a),
102 | when({ b: defined }, ({ b }) => b.then(() => 42)),
103 | otherwise(async () => await somethingThatRejects())
104 | ) // produces a Promise
105 |
106 | export const BuiltInCustomMatchers = (value) =>
107 | match(value)(
108 | when(matchNumber(), (value) => console.log('Number', value)),
109 | when(matchBigInt(), (value) => console.log('BigInt', value)),
110 | when(matchString(), (value) => console.log('String', value)),
111 | when(matchArray(), (value) => console.log('Array', value)),
112 | otherwise((value) => console.log('otherwise', value))
113 | )
114 |
115 | export const ChainingGuards = (res) =>
116 | match(res)(
117 | when({ pages: greaterThan(1), data: defined }, () =>
118 | console.log('multiple pages')
119 | ),
120 | when({ pages: 1, data: defined }, () => console.log('one page')),
121 | otherwise(() => console.log('no pages'))
122 | )
123 |
124 | export const ConditionalJsx = (h, API_URL, Fetch, Loading, Error, Page) => () =>
125 | h(Fetch, { url: API_URL }, (props) =>
126 | match(props)(
127 | when({ loading: defined }, () => h(Loading)),
128 | when({ error: defined }, ({ error }) => {
129 | console.err('something bad happened')
130 | return h(Error, { error })
131 | }),
132 | when({ data: defined }, ({ data }) => h(Page, { data }))
133 | )
134 | )
135 |
136 | export class Foo {
137 | static [matcher](value) {
138 | return {
139 | matched: value instanceof Foo,
140 | value,
141 | }
142 | }
143 | }
144 |
145 | const Exception = Error
146 |
147 | export class Option {
148 | constructor(hasValue, value) {
149 | this.hasValue = !!hasValue
150 | if (hasValue) {
151 | this._value = value
152 | }
153 | }
154 | get value() {
155 | if (this.hasValue) return this._value
156 | throw new Exception("Can't get the value of an Option.None.")
157 | }
158 |
159 | static Some(val) {
160 | return new Option(true, val)
161 | }
162 | static None() {
163 | return new Option(false)
164 | }
165 | }
166 |
167 | Option.Some[matcher] = ValueMatcher((val) => ({
168 | matched: val instanceof Option && val.hasValue,
169 | value: val instanceof Option && val.hasValue && val.value,
170 | }))
171 | Option.None[matcher] = ValueMatcher((val) => ({
172 | matched: val instanceof Option && !val.hasValue,
173 | }))
174 |
175 | export const CustomMatcherOption = (result) =>
176 | match(result)(
177 | when(Option.Some, (val) => console.log(val)),
178 | when(Option.None, () => console.log('none'))
179 | )
180 |
181 | const RequestError = Error
182 |
183 | export const FetchJsonResponse = async (fetch, jsonService) => {
184 | const res = await fetch(jsonService)
185 | match(res)(
186 | when(
187 | { status: 200, headers: { 'Content-Length': defined } },
188 | ({ headers: { 'Content-Length': s } }) => console.log(`size is ${s}`)
189 | ),
190 | when({ status: 404 }, () => console.log('JSON not found')),
191 | when({ status: greaterThanEquals(400) }, () => {
192 | throw new RequestError(res)
193 | })
194 | )
195 | }
196 |
197 | export const NilPattern = (someArr) =>
198 | match(someArr)(
199 | when([any, any, defined], ([, , someVal]) => console.log(someVal))
200 | )
201 |
202 | export const ObjectPatternCaching = () => {
203 | const randomItem = {
204 | get numOrString() {
205 | return Math.random() < 0.5 ? 1 : '1'
206 | },
207 | }
208 |
209 | match(randomItem)(
210 | when({ numOrString: matchNumber() }, () =>
211 | console.log('Only matches half the time.')
212 | ),
213 | // Whether the pattern matches or not,
214 | // we cache the (randomItem, "numOrString") pair
215 | // with the result.
216 | when({ numOrString: matchString() }, () =>
217 | console.log('Guaranteed to match the other half of the time.')
218 | )
219 | // Since (randomItem, "numOrString") has already been cached,
220 | // we reuse the result here;
221 | // if it was a string for the first clause,
222 | // it’s the same string here.
223 | )
224 | }
225 |
226 | export const RegExpGroups = (process, handleOtherwise) => (arithmeticStr) =>
227 | match(arithmeticStr)(
228 | when(
229 | /(?