patroon 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # Patroon
2
+
3
+ Patroon.js as an attempt to add pattern matching-ish functionality without
4
+ introducing new syntax.
5
+
6
+ ## Implementation
7
+
8
+ 1. [./src/walkable.js][2] - Patroon allows one to define deeply nested
9
+ patterns. To implement these features succinctly we use an abstraction
10
+ which allows the traversing of a tree. This is an area of computer science
11
+ named [tree traversal][1].
12
+ 2. [./src/index.js][3] - We now take the walkable utility and implement
13
+ patroon's functionality.
14
+ 3. [./src/helpers.js][4] - You might have noticed that both the patroon and
15
+ walkable modules have common helper functions.
16
+
17
+ ## Specifications
18
+
19
+ Let's see what valid and less valid uses of patroon are.
20
+
21
+ ### Arrays
22
+
23
+ A less intuitive case (atleast initially) is the matching with an empty array.
24
+
25
+ ```js ./tape-test
26
+ patroon(
27
+ [], end,
28
+ [1], fail
29
+ )([1])
30
+ ```
31
+
32
+ Notice that the empty array watches with `[1]`. This is because the empty array
33
+ is a subset of `[1]`.
34
+
35
+ In this case you might as well write the following for readability sake:
36
+
37
+ ```js ./tape-test
38
+ patroon(
39
+ typed(Array), end,
40
+ )([1])
41
+ ```
42
+
43
+ Patroon even tries to determine if something is a constructor. No need to use
44
+ typed in that case.
45
+
46
+ ```js ./tape-test
47
+ patroon(
48
+ Number, fail,
49
+ Array, end,
50
+ )([1])
51
+ ```
52
+
53
+ If you wish to match on the reference of a constructor you can use the `ref` helper.
54
+
55
+ ```js ./tape-test
56
+ patroon(
57
+ 1, fail,
58
+ Number, fail,
59
+ ref(Number), end,
60
+ )(Number)
61
+ ```
62
+
63
+ Some more array examples to wrap your brain around.
64
+
65
+ ```js ./tape-test
66
+ const arrayMatch = patroon(
67
+ [1,2], () => 2,
68
+ [1,2,3], () => 3,
69
+ [2], () => 1,
70
+ [], () => null
71
+ )
72
+
73
+ t.equal(arrayMatch([1,2]), 2)
74
+ t.equal(arrayMatch([1,2,3]), 2)
75
+ t.equal(arrayMatch([]), null)
76
+ t.equal(arrayMatch([2, 3]), 1)
77
+ t.end()
78
+ ```
79
+
80
+ The array pattern assumes that the array has rest elements. It's a design
81
+ choice which avoids adding additional helpers with little to no downsides.
82
+
83
+ In case the seamingly unexpected case seems truely unexpected; I suggest you
84
+ think of patterns as a subset of the value you are trying to match. In the case
85
+ of arrays. `[1,2]` is a subset of `[1,2,3]`. `[2,3]` is not a subset of
86
+ `[1,2,3]` because arrays also care about the order of elements.
87
+
88
+ Now is a good time to introduce the placeholder(`_`) concept.
89
+
90
+ ### Placeholders
91
+
92
+ ```js ./tape-test
93
+ const alwaysMatches = patroon(_, t.end())('any value really')
94
+ ```
95
+
96
+ A function that looks for a certain pattern in an array.
97
+
98
+ ```js ./tape-test
99
+ const containsPattern = patroon(
100
+ [0, 0], true,
101
+ [_, _], ([, ...rest]) => containsPattern(rest),
102
+ [], false
103
+ )
104
+
105
+ t.true(containsPattern([0,0]))
106
+ t.true(containsPattern([1,0,0]))
107
+ t.false(containsPattern([1,0,1]))
108
+ t.true(containsPattern([1,0,1,0,0]))
109
+ t.end()
110
+ ```
111
+
112
+ A toPairs function:
113
+
114
+ ```js ./tape-test
115
+ const toPairs = patroon(
116
+ [_, _], ([a, b, ...c], p = []) => toPairs(c, [...p, [a, b]]),
117
+ _, (_, p = []) => p
118
+ )
119
+
120
+ t.deepEquals(toPairs([1]), [])
121
+ t.deepEquals(toPairs([1, 2]), [[1, 2]])
122
+ t.deepEquals(toPairs([1, 2, 3]), [[1, 2]])
123
+ t.deepEquals(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
124
+ t.end()
125
+ ```
126
+
127
+ > An exercise would be to change toPairs to throw when an uneven length array
128
+ > is passed. Multiple answers are possible and some are more optimized than
129
+ > others.
130
+
131
+ So that's arrays. What about objects.
132
+
133
+ ### Objects
134
+
135
+ Just like an empty array; matching on an empty object can be written in two
136
+ ways.
137
+
138
+ ```js ./tape-test
139
+ patroon(
140
+ {}, pass,
141
+ )({a: 1})
142
+
143
+ patroon(
144
+ typed(Object), pass,
145
+ )({a: 1})
146
+
147
+ patroon(
148
+ Object, end,
149
+ )({a: 1})
150
+ ```
151
+
152
+ Next we match on the existance of object keys. We use the `_` to
153
+ achieve this.
154
+
155
+ ```js ./tape-test
156
+ patroon(
157
+ {a: _}, end
158
+ )({a: 2})
159
+ ```
160
+
161
+ Next we also match on the key's value.
162
+
163
+ ```js ./tape-test
164
+ patroon(
165
+ {a: 1}, fail,
166
+ {a: 2}, end,
167
+ {a: 3}, fail
168
+ )({a: 2})
169
+ ```
170
+
171
+ What about nested objects? No problem!
172
+
173
+ ```js ./tape-test
174
+ patroon(
175
+ {a: {a: 1}}, fail,
176
+ {a: {a: 2}}, end,
177
+ {a: {a: 3}}, fail
178
+ )({a: {a: 2}})
179
+ ```
180
+
181
+ ### Types
182
+
183
+ We'll match on type using `typed` which internally uses `instanceof`.
184
+
185
+
186
+ ```js ./tape-test
187
+ patroon(
188
+ typed(TypeError), fail,
189
+ typed(Error), pass
190
+ )(new Error())
191
+
192
+ patroon(
193
+ TypeError, end,
194
+ Error, fail
195
+ )(new TypeError())
196
+ ```
197
+
198
+ An object of a certain type might also have values we would want to match on.
199
+
200
+ ```js ./tape-test
201
+ patroon(
202
+ typed(TypeError, { value: 20 }), fail,
203
+ typed(Error, { value: 30 }), fail,
204
+ typed(Error, { value: 20 }), end
205
+ )(Object.assign(new Error(), { value: 20 }))
206
+ ```
207
+
208
+ Simply pass a pattern as the second argument of typed.
209
+
210
+ Now we'll try predicates.
211
+
212
+ ### Predicates
213
+
214
+ By default a function is assumed to be a predicate.
215
+
216
+ ```js ./tape-test
217
+ const isTrue = v => v === true
218
+
219
+ patroon(
220
+ isTrue, end
221
+ )(true)
222
+ ```
223
+
224
+ You might have a case where you want to match on the reference of a function.
225
+ Some people are weird like that. In that case one can use the ref helper.
226
+
227
+ ```js ./tape-test
228
+ const fun = () => false
229
+
230
+ patroon(
231
+ fun, fail,
232
+ ref(fun), end
233
+ )(fun)
234
+ ```
235
+
236
+ Could one combine predicates with arrays and objects? Sure one can!
237
+
238
+ ```js ./tape-test
239
+ const is20 = v => v === 20
240
+
241
+ patroon(
242
+ [[is20]], end,
243
+ )([[20]])
244
+ ```
245
+
246
+ ```js ./tape-test
247
+ const is42 = v => v === 42
248
+
249
+ patroon(
250
+ [{a: is42}], end,
251
+ )([{a: 42}])
252
+ ```
253
+
254
+ ## Tests
255
+
256
+ Now for some additional edge cases and some generative testing.
257
+ [./src/index.test.js][5]
258
+
259
+ ```bash bash
260
+ npm test
261
+ ```
262
+ ```
263
+
264
+ > patroon@0.0.0 test
265
+ > tape ./src/index.test.js
266
+
267
+ TAP version 13
268
+ # Matches always when pattern equals value
269
+ # Matches none of the patterns and throws
270
+ ok 1 should be truthy
271
+ # Does not match when a value does not exist
272
+ # Throws when a typed does not receice a constructor
273
+ ok 2 should be truthy
274
+ # Throws in a predicate function
275
+ ok 3 should be truthy
276
+ # Throws when an uneven amount of arguments are passed
277
+ ok 4 should be truthy
278
+
279
+ 1..4
280
+ # tests 4
281
+ # pass 4
282
+
283
+ # ok
284
+
285
+ ```
286
+
287
+ ## Formatting
288
+
289
+ Standard is good enough.
290
+
291
+ ```bash bash 2>&1
292
+ npx standard || npx standard --fix
293
+ ```
294
+
295
+ [1]:https://en.wikipedia.org/wiki/Tree_traversal
296
+ [2]:./src/walkable.js
297
+ [3]:./src/index.js
298
+ [4]:./src/helpers.js
299
+ [5]:./src/index.test.js
package/README.mz ADDED
@@ -0,0 +1,275 @@
1
+ # Patroon
2
+
3
+ Patroon.js as an attempt to add pattern matching-ish functionality without
4
+ introducing new syntax.
5
+
6
+ ## Implementation
7
+
8
+ 1. [./src/walkable.js][2] - Patroon allows one to define deeply nested
9
+ patterns. To implement these features succinctly we use an abstraction
10
+ which allows the traversing of a tree. This is an area of computer science
11
+ named [tree traversal][1].
12
+ 2. [./src/index.js][3] - We now take the walkable utility and implement
13
+ patroon's functionality.
14
+ 3. [./src/helpers.js][4] - You might have noticed that both the patroon and
15
+ walkable modules have common helper functions.
16
+
17
+ ## Specifications
18
+
19
+ Let's see what valid and less valid uses of patroon are.
20
+
21
+ ### Arrays
22
+
23
+ A less intuitive case (atleast initially) is the matching with an empty array.
24
+
25
+ ```js ./tape-test
26
+ patroon(
27
+ [], end,
28
+ [1], fail
29
+ )([1])
30
+ ```
31
+
32
+ Notice that the empty array watches with `[1]`. This is because the empty array
33
+ is a subset of `[1]`.
34
+
35
+ In this case you might as well write the following for readability sake:
36
+
37
+ ```js ./tape-test
38
+ patroon(
39
+ typed(Array), end,
40
+ )([1])
41
+ ```
42
+
43
+ Patroon even tries to determine if something is a constructor. No need to use
44
+ typed in that case.
45
+
46
+ ```js ./tape-test
47
+ patroon(
48
+ Number, fail,
49
+ Array, end,
50
+ )([1])
51
+ ```
52
+
53
+ If you wish to match on the reference of a constructor you can use the `ref` helper.
54
+
55
+ ```js ./tape-test
56
+ patroon(
57
+ 1, fail,
58
+ Number, fail,
59
+ ref(Number), end,
60
+ )(Number)
61
+ ```
62
+
63
+ Some more array examples to wrap your brain around.
64
+
65
+ ```js ./tape-test
66
+ const arrayMatch = patroon(
67
+ [1,2], () => 2,
68
+ [1,2,3], () => 3,
69
+ [2], () => 1,
70
+ [], () => null
71
+ )
72
+
73
+ t.equal(arrayMatch([1,2]), 2)
74
+ t.equal(arrayMatch([1,2,3]), 2)
75
+ t.equal(arrayMatch([]), null)
76
+ t.equal(arrayMatch([2, 3]), 1)
77
+ t.end()
78
+ ```
79
+
80
+ The array pattern assumes that the array has rest elements. It's a design
81
+ choice which avoids adding additional helpers with little to no downsides.
82
+
83
+ In case the seamingly unexpected case seems truely unexpected; I suggest you
84
+ think of patterns as a subset of the value you are trying to match. In the case
85
+ of arrays. `[1,2]` is a subset of `[1,2,3]`. `[2,3]` is not a subset of
86
+ `[1,2,3]` because arrays also care about the order of elements.
87
+
88
+ Now is a good time to introduce the placeholder(`_`) concept.
89
+
90
+ ### Placeholders
91
+
92
+ ```js ./tape-test
93
+ const alwaysMatches = patroon(_, t.end())('any value really')
94
+ ```
95
+
96
+ A function that looks for a certain pattern in an array.
97
+
98
+ ```js ./tape-test
99
+ const containsPattern = patroon(
100
+ [0, 0], true,
101
+ [_, _], ([, ...rest]) => containsPattern(rest),
102
+ [], false
103
+ )
104
+
105
+ t.true(containsPattern([0,0]))
106
+ t.true(containsPattern([1,0,0]))
107
+ t.false(containsPattern([1,0,1]))
108
+ t.true(containsPattern([1,0,1,0,0]))
109
+ t.end()
110
+ ```
111
+
112
+ A toPairs function:
113
+
114
+ ```js ./tape-test
115
+ const toPairs = patroon(
116
+ [_, _], ([a, b, ...c], p = []) => toPairs(c, [...p, [a, b]]),
117
+ _, (_, p = []) => p
118
+ )
119
+
120
+ t.deepEquals(toPairs([1]), [])
121
+ t.deepEquals(toPairs([1, 2]), [[1, 2]])
122
+ t.deepEquals(toPairs([1, 2, 3]), [[1, 2]])
123
+ t.deepEquals(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
124
+ t.end()
125
+ ```
126
+
127
+ > An exercise would be to change toPairs to throw when an uneven length array
128
+ > is passed. Multiple answers are possible and some are more optimized than
129
+ > others.
130
+
131
+ So that's arrays. What about objects.
132
+
133
+ ### Objects
134
+
135
+ Just like an empty array; matching on an empty object can be written in two
136
+ ways.
137
+
138
+ ```js ./tape-test
139
+ patroon(
140
+ {}, pass,
141
+ )({a: 1})
142
+
143
+ patroon(
144
+ typed(Object), pass,
145
+ )({a: 1})
146
+
147
+ patroon(
148
+ Object, end,
149
+ )({a: 1})
150
+ ```
151
+
152
+ Next we match on the existance of object keys. We use the `_` to
153
+ achieve this.
154
+
155
+ ```js ./tape-test
156
+ patroon(
157
+ {a: _}, end
158
+ )({a: 2})
159
+ ```
160
+
161
+ Next we also match on the key's value.
162
+
163
+ ```js ./tape-test
164
+ patroon(
165
+ {a: 1}, fail,
166
+ {a: 2}, end,
167
+ {a: 3}, fail
168
+ )({a: 2})
169
+ ```
170
+
171
+ What about nested objects? No problem!
172
+
173
+ ```js ./tape-test
174
+ patroon(
175
+ {a: {a: 1}}, fail,
176
+ {a: {a: 2}}, end,
177
+ {a: {a: 3}}, fail
178
+ )({a: {a: 2}})
179
+ ```
180
+
181
+ ### Types
182
+
183
+ We'll match on type using `typed` which internally uses `instanceof`.
184
+
185
+
186
+ ```js ./tape-test
187
+ patroon(
188
+ typed(TypeError), fail,
189
+ typed(Error), pass
190
+ )(new Error())
191
+
192
+ patroon(
193
+ TypeError, end,
194
+ Error, fail
195
+ )(new TypeError())
196
+ ```
197
+
198
+ An object of a certain type might also have values we would want to match on.
199
+
200
+ ```js ./tape-test
201
+ patroon(
202
+ typed(TypeError, { value: 20 }), fail,
203
+ typed(Error, { value: 30 }), fail,
204
+ typed(Error, { value: 20 }), end
205
+ )(Object.assign(new Error(), { value: 20 }))
206
+ ```
207
+
208
+ Simply pass a pattern as the second argument of typed.
209
+
210
+ Now we'll try predicates.
211
+
212
+ ### Predicates
213
+
214
+ By default a function is assumed to be a predicate.
215
+
216
+ ```js ./tape-test
217
+ const isTrue = v => v === true
218
+
219
+ patroon(
220
+ isTrue, end
221
+ )(true)
222
+ ```
223
+
224
+ You might have a case where you want to match on the reference of a function.
225
+ Some people are weird like that. In that case one can use the ref helper.
226
+
227
+ ```js ./tape-test
228
+ const fun = () => false
229
+
230
+ patroon(
231
+ fun, fail,
232
+ ref(fun), end
233
+ )(fun)
234
+ ```
235
+
236
+ Could one combine predicates with arrays and objects? Sure one can!
237
+
238
+ ```js ./tape-test
239
+ const is20 = v => v === 20
240
+
241
+ patroon(
242
+ [[is20]], end,
243
+ )([[20]])
244
+ ```
245
+
246
+ ```js ./tape-test
247
+ const is42 = v => v === 42
248
+
249
+ patroon(
250
+ [{a: is42}], end,
251
+ )([{a: 42}])
252
+ ```
253
+
254
+ ## Tests
255
+
256
+ Now for some additional edge cases and some generative testing.
257
+ [./src/index.test.js][5]
258
+
259
+ ```bash bash
260
+ npm test
261
+ ```
262
+
263
+ ## Formatting
264
+
265
+ Standard is good enough.
266
+
267
+ ```bash bash 2>&1
268
+ npx standard || npx standard --fix
269
+ ```
270
+
271
+ [1]:https://en.wikipedia.org/wiki/Tree_traversal
272
+ [2]:./src/walkable.js
273
+ [3]:./src/index.js
274
+ [4]:./src/helpers.js
275
+ [5]:./src/index.test.js
File without changes
@@ -0,0 +1,9 @@
1
+
2
+ This would be neat.
3
+
4
+ ```js
5
+ patroon(
6
+ "hello", true,
7
+ "world", false
8
+ )("hello world")
9
+ ```
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "patroon",
3
+ "version": "0.0.0",
4
+ "description": "Pattern matching library",
5
+ "main": "./src/index.js",
6
+ "scripts": {
7
+ "test": "tape ./src/index.test.js"
8
+ },
9
+ "keywords": [
10
+ "pattern",
11
+ "matching"
12
+ ],
13
+ "author": "Bassim Huis",
14
+ "devDependencies": {
15
+ "tape": "^5.1.1",
16
+ "tape-check": "^1.0.0-rc.0",
17
+ "testcheck": "^1.0.0-rc.2"
18
+ }
19
+ }
package/src/helpers.js ADDED
@@ -0,0 +1,52 @@
1
+ const toPairs = items => {
2
+ if (items.length < 2) { return [] }
3
+
4
+ const [a, b, ...rest] = items
5
+
6
+ return [[a, b], ...toPairs(rest)]
7
+ }
8
+
9
+ const isNil = x => !(x != null)
10
+
11
+ function isConstructor (func) {
12
+ return Boolean(func && typeof func === 'function' && func.prototype && func.prototype.constructor)
13
+ }
14
+
15
+ module.exports = {
16
+ always: x => () => x,
17
+ isConstructor,
18
+ toPairs,
19
+ isNil,
20
+ isEven (x) {
21
+ return x % 2 === 0
22
+ },
23
+ is: Ctor => {
24
+ if (!isConstructor(Ctor)) {
25
+ throw new Error('Ctor is not a constructor')
26
+ }
27
+
28
+ return instance =>
29
+ instance.constructor === Ctor || instance instanceof Ctor
30
+ },
31
+ tryCatch (tryFn, catchFn) {
32
+ return (...args) => {
33
+ try {
34
+ return tryFn(...args)
35
+ } catch (e) {
36
+ return catchFn(e)
37
+ }
38
+ }
39
+ },
40
+ T () {
41
+ return true
42
+ },
43
+ equals (a, b) {
44
+ return a === b || Object.is(a, b)
45
+ },
46
+ isFunction (x) {
47
+ return typeof x === 'function'
48
+ },
49
+ isDefined (x) {
50
+ return x != null
51
+ }
52
+ }
package/src/index.js ADDED
@@ -0,0 +1,73 @@
1
+ const { mapLeaves, path, PathError } = require('./walkable')()
2
+ const { isConstructor, isFunction, equals, T, is, tryCatch, isEven, isNil, toPairs, always } = require('./helpers')
3
+
4
+ const match = pattern => {
5
+ // TODO: also check if something is a constructor
6
+
7
+ const patternPredicates = mapLeaves(
8
+ (value, pth) => tryCatch(
9
+ isConstructor(value)
10
+ ? typed(value)
11
+ : (isFunction(value)
12
+ ? arg => value(path(pth, arg))
13
+ : arg => equals(path(pth, arg), value)),
14
+ e => {
15
+ if (e instanceof PathError) { return false }
16
+
17
+ throw e // How to test this case?
18
+ }
19
+ ),
20
+ pattern
21
+ )
22
+
23
+ return (...args) => patternPredicates.every(pred => pred(...args))
24
+ }
25
+
26
+ class NoMatchError extends Error {
27
+ constructor (...args) {
28
+ super(...args)
29
+ this.name = 'NoMatchError'
30
+ }
31
+ }
32
+ const toFunction = x => isFunction(x) ? x : always(x)
33
+
34
+ const patroon = (...list) => {
35
+ if (!isEven(list.length)) { throw new TypeError('Patroon should have even amount of arguments.') }
36
+
37
+ const patterns = toPairs(list).map(([pattern, doFn]) => [match(pattern), toFunction(doFn)])
38
+
39
+ return (...args) => {
40
+ const found = patterns.find(([matches]) => matches(...args))
41
+
42
+ if (isNil(found)) { throw new NoMatchError(`Not able to match any pattern for value ${JSON.stringify(args)}`) }
43
+
44
+ const [, doFn] = found
45
+
46
+ return doFn(...args)
47
+ }
48
+ }
49
+
50
+ function typed (Ctor, ...args) {
51
+ if (args.length === 0) {
52
+ return is(Ctor)
53
+ }
54
+
55
+ const [pattern] = args
56
+
57
+ return (instance) =>
58
+ typed(Ctor)(instance) &&
59
+ match(pattern)(instance)
60
+ }
61
+
62
+ function ref (fn) {
63
+ return (arg) => fn === arg
64
+ }
65
+
66
+ module.exports = Object.assign(patroon, {
67
+ NoMatchError,
68
+ patroon,
69
+ ref,
70
+ _: T,
71
+ typed,
72
+ t: typed
73
+ })