Comparing version 0.24.1 to 0.24.3
@@ -110,2 +110,4 @@ # Creating Testable Bots by Separating Concerns | ||
Another problem with this approach is that every piece of functionality in `bot` probably needs to be mocked, e.g. `fs.delete(...)`. | ||
This is a direct result of the imperative style used to code `bot` - at some point a decision is made to take an action, the action is taken, and that's that. It's hard to test. | ||
@@ -124,3 +126,3 @@ | ||
```ts | ||
export class ActionReference { | ||
class ActionReference { | ||
@@ -127,0 +129,0 @@ constructor ( |
@@ -26,10 +26,11 @@ # Scoring | ||
const returnsNull = Scored.from(null); // null | ||
const returnsNull = Scored.from(undefined); // null | ||
const returnsNull = Scored.from("Bill", 0); // null | ||
``` | ||
Scoring is usually a temporary operation - you wrap results with scores to determine the highest one(s). To unwrap them call `Scored.unwrap`, which will return the wrapped result for any `Scored` and pass through any other results. | ||
Scoring is usually a temporary operation - you wrap results with scores to determine the highest one(s), then unwrap the winner(s). To unwrap a result, call `Scored.unwrap`, which will return the wrapped result for any `Scored` and pass through any other results. | ||
```ts | ||
Scored.unwrap(iffyBill); // "Bill" | ||
Scored.unwrap("Bill"); // "Bill" | ||
Scored.unwrap(iffyBill); // "Bill" | ||
Scored.unwrap("Bill"); // "Bill" | ||
``` | ||
@@ -41,7 +42,7 @@ | ||
In this example we are maintaining `botstate` which will track | ||
In this example we are maintaining `botstate` which will track whether the bot has asked the user a question. | ||
have a `Transform` which always assigns a score of 1 to a name gleaned from an unambiguously verbose introduction. Otherwise, if there is an outstanding question (the bot previously asked the user's name) it assigns a 50% chance that the entire user's response is a name. In either case we transform that `Scored string` into a `Scored ActionReference` with the same score, greeting the user. | ||
We will add a `Transform` which always assigns a score of 1 to a name gleaned from an unambiguously verbose introduction. Otherwise, if there is an outstanding question (the bot previously asked the user's name) it assigns a 50% chance that the entire user's response is a name. In either case we transform that `Scored string` into a `Scored ActionReference` with the same score, greeting the user. | ||
Meanwhile we have a different `Transform` that is looking for the phrase "current time". If there are no outstanding questions it returns its action with a score of 1, but even if there is an outstanding question we consider that there's a pretty good chance that this phrase represents a command, so it assigns a score of .75. | ||
Meanwhile we will add a different `Transform` that is looking for the phrase "current time". If there are no outstanding questions it returns its action with a score of 1, but even if there is an outstanding question we consider that there's a pretty good chance that this phrase represents a command, so it assigns a score of .75. | ||
@@ -61,22 +62,24 @@ We pass both these transforms to another *Prague* function, `best`, which returns a new transform which calls *all* of the transforms, collects the `Scored`s thereof, and returns the unwrapped result of the highest scoring one. | ||
// all the existing transforms plus: | ||
match( | ||
first( | ||
match( | ||
re(/My name is (.*)/i, 1), | ||
name => Scored.from(name), | ||
best( | ||
match( | ||
first( | ||
match( | ||
re(/My name is (.*)/i, 1), | ||
Scored.from, | ||
), | ||
t => botstate.question === 'name' ? Scored.from(t, .5) : null, | ||
), | ||
t => botstate.question === 'name' ? Scored.from(t, .5) : null, | ||
scoredName => Scored.from( | ||
actions.reference.greet(scoredName.result), | ||
scoredName.score | ||
), | ||
), | ||
scoredName => Scored.from( | ||
actions.reference.greet(scoredName.result), | ||
scoredName.score | ||
), | ||
), | ||
match( | ||
re(/current time/), | ||
() => Scored.from( | ||
actions.reference.time(), | ||
botstate.question ? .75 : 1 | ||
match( | ||
re(/current time/), | ||
() => Scored.from( | ||
actions.reference.time(), | ||
botstate.question ? .75 : 1 | ||
) | ||
) | ||
), | ||
) | ||
); | ||
@@ -116,14 +119,8 @@ | ||
The first thing we need is a way to work with more than one result. Enter `Multiple`, which wraps an array of results. You can either create one directly: | ||
To work with multiple results, simply create an array. You can do it statically, as with `values`, or you can use the `multiple` helper to create a `Transform` which calls each supplied `Transform`. If all return `null`, it returns `null`. If only one returns a result, it returns that. If two or more return results, it returns an Array containing them. `multiple` also flattens any results that are themselves arrays. | ||
```ts | ||
new Multiple(values); | ||
multiple(...valueTransforms); | ||
``` | ||
Or you can use the `multiple` helper to create a `Transform` which calls each supplied `Transform`. If all return `null`, it returns `null`. If one returns a result, it returns that. If two or more return results, it returns a `Multiple` containing them. | ||
```ts | ||
multiple(valueTransforms); | ||
``` | ||
Frequently the thing you want to do with multiple results is to sort them: | ||
@@ -133,8 +130,8 @@ | ||
const sortme = pipe( | ||
multiple(valueTransforms), | ||
sort(), // sort(true) for ascending, sort(false) for descending (this is the default) | ||
multiple(...valueTransforms), | ||
sort(), // sort(true) for ascending, sort() or sort(false) for descending | ||
) | ||
``` | ||
Thus `sortme()` returns a `Multiple` which contains a sorted array of strings. | ||
Thus `sortme()` returns a sorted array of strings. | ||
@@ -149,3 +146,3 @@ We can narrow down this result using yet another helper called `top`. | ||
top(), | ||
)() // Multiple{ results:[ Scored{ result: "hi", score: .75 }, Scored{ result: "hello", score: .75 } ] } | ||
)() // [ Scored{ result: "hi", score: .75 }, Scored{ result: "hello", score: .75 } ] | ||
``` | ||
@@ -161,3 +158,3 @@ | ||
}), | ||
)() // Multiple{ results:[ Scored{ result: "hi", score: .75 }, Scored{ result: "hello", score: .75 }, Scored{ result: "aloha", score: .70 } ] } | ||
)() // [ Scored{ result: "hi", score: .75 }, Scored{ result: "hello", score: .75 }, Scored{ result: "aloha", score: .70 } ] | ||
``` | ||
@@ -174,3 +171,3 @@ | ||
}), | ||
)() // Multiple{ results:[ Scored{ result: "hi", score: .75 }, Scored{ result: "hello", score: .75 }, Scored{ result: "aloha", score: .70 }, ] } | ||
)() // [ Scored{ result: "hi", score: .75 }, Scored{ result: "hello", score: .75 }, Scored{ result: "aloha", score: .70 }, ] | ||
``` | ||
@@ -199,3 +196,3 @@ | ||
In this chapter we introduced the idea of *scoring* results, and the tools necessary to reason about them: the classes `Scored` (with its methods `from` and `unwrap`) and `Multiple`, and the helper functions `multiple`, `sort`, and `top`. | ||
In this chapter we introduced the idea of *scoring* results, and the tools necessary to reason about them: `Scored` (with its methods `from` and `unwrap`) and the helper functions `multiple`, `sort`, and `top`. | ||
@@ -202,0 +199,0 @@ ## Next |
@@ -23,2 +23,4 @@ # More *Prague* | ||
## | ||
## combine | ||
tk |
import { Transform, Returns } from "./prague"; | ||
export declare class Multiple { | ||
results: any[]; | ||
constructor(results: any[]); | ||
} | ||
declare type MaybeArray<T> = [T] extends [never] ? never : T | Array<T>; | ||
declare type NullIfNull<T> = NonNullable<T> extends never ? null : never; | ||
declare type F<T> = NonNullable<T> extends never ? never : T; | ||
declare type Flatten<T> = T extends Array<infer U> ? U : T; | ||
export declare function multiple(): Transform<[], null>; | ||
export declare function multiple<ARGS extends any[], R0>(...transforms: [(...args: ARGS) => Returns<R0>]): Transform<ARGS, R0>; | ||
export declare function multiple<ARGS extends any[], R0, R1>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>]): Transform<ARGS, R0 | R1 | Multiple>; | ||
export declare function multiple<ARGS extends any[], R0, R1, R2>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>, (...args: ARGS) => Returns<R2>]): Transform<ARGS, R0 | R1 | R2 | Multiple>; | ||
export declare function multiple<ARGS extends any[], R0, R1, R2, R3>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>, (...args: ARGS) => Returns<R2>, (...args: ARGS) => Returns<R3>]): Transform<ARGS, R0 | R1 | R2 | R3 | Multiple>; | ||
export declare function multiple<ARGS extends any[], R0, R1, R2, R3, R4>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>, (...args: ARGS) => Returns<R2>, (...args: ARGS) => Returns<R3>, (...args: ARGS) => Returns<R4>]): Transform<ARGS, R0 | R1 | R2 | R3 | R4 | Multiple>; | ||
export declare function multiple<ARGS extends any[], R0, R1>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>]): Transform<ARGS, MaybeArray<NonNullable<Flatten<R0> | Flatten<R1>>> | NullIfNull<F<R0> | F<R1>>>; | ||
export declare function multiple<ARGS extends any[], R0, R1, R2>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>, (...args: ARGS) => Returns<R2>]): Transform<ARGS, MaybeArray<NonNullable<Flatten<R0> | Flatten<R1> | Flatten<R2>>> | NullIfNull<F<R0> | F<R1> | F<R2>>>; | ||
export declare function multiple<ARGS extends any[], R0, R1, R2, R3>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>, (...args: ARGS) => Returns<R2>, (...args: ARGS) => Returns<R3>]): Transform<ARGS, MaybeArray<NonNullable<Flatten<R0> | Flatten<R1> | Flatten<R2> | Flatten<R3>>> | NullIfNull<F<R0> | F<R1> | F<R2> | F<R3>>>; | ||
export declare function multiple<ARGS extends any[], R0, R1, R2, R3, R4>(...transforms: [(...args: ARGS) => Returns<R0>, (...args: ARGS) => Returns<R1>, (...args: ARGS) => Returns<R2>, (...args: ARGS) => Returns<R3>, (...args: ARGS) => Returns<R4>]): Transform<ARGS, MaybeArray<NonNullable<Flatten<R0> | Flatten<R1> | Flatten<R2> | Flatten<R3> | Flatten<R4>>> | NullIfNull<F<R0> | F<R1> | F<R3> | F<R4>>>; | ||
export declare function multiple<ARGS extends any[], O>(...args: ((...args: ARGS) => Returns<O>)[]): Transform<ARGS, NonNullable<O> | NullIfNull<O>>; | ||
export declare function multiple<ARGS extends any[]>(...args: ((...args: ARGS) => any)[]): Transform<ARGS, any>; | ||
export {}; |
@@ -6,8 +6,2 @@ "use strict"; | ||
const operators_1 = require("rxjs/operators"); | ||
class Multiple { | ||
constructor(results) { | ||
this.results = results; | ||
} | ||
} | ||
exports.Multiple = Multiple; | ||
function multiple(...transforms) { | ||
@@ -17,7 +11,7 @@ if (transforms.length === 0) | ||
const _transforms = rxjs_1.from(transforms.map(transform => prague_1.from(transform))); | ||
return ((...args) => _transforms.pipe(operators_1.flatMap(transform => transform(...args)), prague_1.filterOutNull, operators_1.flatMap(result => result instanceof Multiple ? rxjs_1.from(result.results) : rxjs_1.of(result)), operators_1.toArray(), operators_1.map(results => results.length === 0 ? null : | ||
return (...args) => _transforms.pipe(operators_1.flatMap(transform => transform(...args)), prague_1.filterOutNull, operators_1.flatMap(result => Array.isArray(result) ? rxjs_1.from(result) : rxjs_1.of(result)), operators_1.toArray(), operators_1.map(results => results.length === 0 ? null : | ||
results.length === 1 ? results[0] : | ||
new Multiple(results)))); | ||
results)); | ||
} | ||
exports.multiple = multiple; | ||
//# sourceMappingURL=multiple.js.map |
@@ -12,3 +12,3 @@ import { Transform } from './prague'; | ||
} | ||
export declare const sort: (ascending?: boolean) => Transform<[any], any>; | ||
export declare const sort: <O>(ascending?: boolean) => (o: O) => O | Scored<any>[]; | ||
export interface TopOptions { | ||
@@ -15,0 +15,0 @@ maxResults?: number; |
@@ -40,6 +40,7 @@ "use strict"; | ||
exports.Scored = Scored; | ||
exports.sort = (ascending = false) => prague_1.transformInstance(prague_1.Multiple, r => new prague_1.Multiple(r | ||
.results | ||
.map(result => Scored.from(result)) | ||
.sort((a, b) => ascending ? (a.score - b.score) : (b.score - a.score)))); | ||
exports.sort = (ascending = false) => (o) => Array.isArray(o) | ||
? o | ||
.map(result => Scored.from(result)) | ||
.sort((a, b) => ascending ? (a.score - b.score) : (b.score - a.score)) | ||
: o; | ||
function top(options) { | ||
@@ -60,12 +61,13 @@ let maxResults = Number.POSITIVE_INFINITY; | ||
} | ||
return prague_1.transformInstance(prague_1.Multiple, multiple => { | ||
const result = multiple.results[0]; | ||
if (!(result instanceof Scored)) | ||
throw "top must only be called on Multiple of Scored"; | ||
const highScore = result.score; | ||
return rxjs_1.from(multiple.results).pipe(operators_1.tap(result => { | ||
if (!(result instanceof Scored)) | ||
throw "top must only be called on Multiple of Scored"; | ||
}), operators_1.takeWhile((m, i) => i < maxResults && m.score + tolerance >= highScore), operators_1.toArray(), operators_1.map(results => results.length === 1 ? results[0] : new prague_1.Multiple(results))); | ||
}); | ||
return (result) => { | ||
if (!Array.isArray(result)) | ||
return rxjs_1.of(result); | ||
let highScore; | ||
return rxjs_1.from(result).pipe(operators_1.tap(_result => { | ||
if (!(_result instanceof Scored)) | ||
throw "top must only be called on Array of Scored"; | ||
if (highScore === undefined) | ||
highScore = _result.score; | ||
}), operators_1.takeWhile((m, i) => i < maxResults && m.score + tolerance >= highScore), operators_1.toArray(), operators_1.map(results => results.length === 1 ? results[0] : results)); | ||
}; | ||
} | ||
@@ -72,0 +74,0 @@ exports.top = top; |
{ | ||
"name": "prague", | ||
"version": "0.24.1", | ||
"version": "0.24.3", | ||
"description": "EDSL for rules", | ||
@@ -5,0 +5,0 @@ "main": "lib/src/prague.js", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
97621