Scorables.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
36 using System;
37 using System.Collections.Generic;
38 using System.Linq;
39 using System.Threading;
40 using System.Threading.Tasks;
41 using System.Text.RegularExpressions;
45 
46 namespace Microsoft.Bot.Builder.Scorables
47 {
48  public static partial class Scorable
49  {
53  public static async Task<bool> TryPostAsync<Item, Score>(this IScorable<Item, Score> scorable, Item item, CancellationToken token)
54  {
55  var state = await scorable.PrepareAsync(item, token);
56  try
57  {
58  if (scorable.HasScore(item, state))
59  {
60  var score = scorable.GetScore(item, state);
61  await scorable.PostAsync(item, state, token);
62  return true;
63  }
64 
65  return false;
66  }
67  finally
68  {
69  await scorable.DoneAsync(item, state, token);
70  }
71  }
72 
73  public static IScorable<Item, Score> WhereScore<Item, Score>(this IScorable<Item, Score> scorable, Func<Item, Score, bool> predicate)
74  {
75  return new WhereScoreScorable<Item, Score>(scorable, predicate);
76  }
77 
81  public static IScorable<Item, TargetScore> SelectScore<Item, SourceScore, TargetScore>(this IScorable<Item, SourceScore> scorable, Func<Item, SourceScore, TargetScore> selector)
82  {
83  return new SelectScoreScorable<Item, SourceScore, TargetScore>(scorable, selector);
84  }
85 
89  public static IScorable<SourceItem, Score> SelectItem<SourceItem, TargetItem, Score>(this IScorable<TargetItem, Score> scorable, Func<SourceItem, TargetItem> selector)
90  {
91  return new SelectItemScorable<SourceItem, TargetItem, Score>(scorable, selector);
92  }
93 
97  public static bool Keep<Item, Score>(IScorable<Item, Score> scorable)
98  {
99  // complicated because IScorable<Item, Score> is variant on generic parameter,
100  // so generic type arguments of NullScorable<,> may not match generic type
101  // arguments of IScorable<,>
102  var type = scorable.GetType();
103  if (type.IsGenericType)
104  {
105  var definition = type.GetGenericTypeDefinition();
106  if (typeof(NullScorable<,>).IsAssignableFrom(definition))
107  {
108  return false;
109  }
110  }
111 
112  return true;
113  }
114 
121  public static bool TryReduce<Item, Score>(ref IEnumerable<IScorable<Item, Score>> scorables, out IScorable<Item, Score> scorable)
122  {
123  // only if this is a fixed list of scorables, but never a lazy enumerable
124  var list = scorables as IReadOnlyList<IScorable<Item, Score>>;
125  if (list != null)
126  {
127  var itemCount = list.Count;
128  int keepCount = 0;
129  for (int index = 0; index < itemCount; ++index)
130  {
131  var item = list[index];
132  if (Keep(item))
133  {
134  ++keepCount;
135  }
136  }
137 
138  // empty non-null list is null scorable
139  if (keepCount == 0)
140  {
142  return true;
143  }
144  // single item non-null list is just that scorable
145  else if (keepCount == 1)
146  {
147  for (int index = 0; index < itemCount; ++index)
148  {
149  var item = list[index];
150  if (Keep(item))
151  {
152  scorable = item;
153  return true;
154  }
155  }
156  }
157  // non-null subset of that list is just those scorables
158  else if (keepCount < itemCount)
159  {
160  var keep = new IScorable<Item, Score>[keepCount];
161  int keepIndex = 0;
162  for (int index = 0; index < itemCount; ++index)
163  {
164  var item = list[index];
165  if (Keep(item))
166  {
167  keep[keepIndex] = item;
168  ++keepIndex;
169  }
170  }
171 
172  scorables = keep;
173  }
174  }
175 
176  scorable = null;
177  return false;
178  }
179 
183  public static IScorable<Item, Score> First<Item, Score>(this IEnumerable<IScorable<Item, Score>> scorables)
184  {
185  IScorable<Item, Score> scorable;
186  if (TryReduce(ref scorables, out scorable))
187  {
188  return scorable;
189  }
190 
191  return new FirstScorable<Item, Score>(scorables);
192  }
193 
197  public static IScorable<Item, Score> Fold<Item, Score>(this IEnumerable<IScorable<Item, Score>> scorables, IComparer<Score> comparer = null, FoldScorable<Item, Score>.OnStageDelegate onStage = null)
198  {
199  comparer = comparer ?? Comparer<Score>.Default;
200 
201  IScorable<Item, Score> scorable;
202  if (TryReduce(ref scorables, out scorable))
203  {
204  return scorable;
205  }
206 
207  return new DelegatingFoldScorable<Item, Score>(onStage, comparer, scorables);
208  }
209  }
210 }
211 
212 namespace Microsoft.Bot.Builder.Scorables.Internals
213 {
214  [Serializable]
215  public sealed class NullScorable<Item, Score> : IScorable<Item, Score>
216  {
217  public static readonly IScorable<Item, Score> Instance = new NullScorable<Item, Score>();
218 
219  private NullScorable()
220  {
221  }
222 
223  Task<object> IScorable<Item, Score>.PrepareAsync(Item item, CancellationToken token)
224  {
225  return Tasks<object>.Null;
226  }
227 
228  bool IScorable<Item, Score>.HasScore(Item item, object state)
229  {
230  return false;
231  }
232 
233  Score IScorable<Item, Score>.GetScore(Item item, object state)
234  {
235  throw new NotImplementedException();
236  }
237 
238  Task IScorable<Item, Score>.PostAsync(Item item, object state, CancellationToken token)
239  {
240  return Task.FromException(new NotImplementedException());
241  }
242 
243  Task IScorable<Item, Score>.DoneAsync(Item item, object state, CancellationToken token)
244  {
245  return Task.CompletedTask;
246  }
247  }
248 
249  [Serializable]
250  public sealed class WhereScoreScorable<Item, Score> : DelegatingScorable<Item, Score>
251  {
252  private readonly Func<Item, Score, bool> predicate;
253 
254  public WhereScoreScorable(IScorable<Item, Score> scorable, Func<Item, Score, bool> predicate)
255  : base(scorable)
256  {
257  SetField.NotNull(out this.predicate, nameof(predicate), predicate);
258  }
259 
260  public override bool HasScore(Item item, object state)
261  {
262  if (base.HasScore(item, state))
263  {
264  var score = base.GetScore(item, state);
265  if (this.predicate(item, score))
266  {
267  return true;
268  }
269  }
270 
271  return false;
272  }
273  }
274 
275  [Serializable]
276  public sealed class SelectItemScorable<OuterItem, InnerItem, Score> : ScorableAggregator<OuterItem, Token<InnerItem, Score>, Score, InnerItem, object, Score>
277  {
278  private readonly IScorable<InnerItem, Score> scorable;
279  private readonly Func<OuterItem, InnerItem> selector;
280 
281  public SelectItemScorable(IScorable<InnerItem, Score> scorable, Func<OuterItem, InnerItem> selector)
282  {
283  SetField.NotNull(out this.scorable, nameof(scorable), scorable);
284  SetField.NotNull(out this.selector, nameof(selector), selector);
285  }
286 
287  protected override async Task<Token<InnerItem, Score>> PrepareAsync(OuterItem sourceItem, CancellationToken token)
288  {
289  var targetItem = this.selector(sourceItem);
290  var state = new Token<InnerItem, Score>()
291  {
292  Item = targetItem,
293  Scorable = this.scorable,
294  State = await this.scorable.PrepareAsync(targetItem, token)
295  };
296  return state;
297  }
298 
299  protected override Score GetScore(OuterItem item, Token<InnerItem, Score> state)
300  {
301  return state.Scorable.GetScore(state.Item, state.State);
302  }
303  }
304 
305  [Serializable]
306  public sealed class SelectScoreScorable<Item, SourceScore, TargetScore> : DelegatingScorable<Item, SourceScore>, IScorable<Item, TargetScore>
307  {
308  private readonly Func<Item, SourceScore, TargetScore> selector;
309 
310  public SelectScoreScorable(IScorable<Item, SourceScore> scorable, Func<Item, SourceScore, TargetScore> selector)
311  : base(scorable)
312  {
313  SetField.NotNull(out this.selector, nameof(selector), selector);
314  }
315 
316  TargetScore IScorable<Item, TargetScore>.GetScore(Item item, object state)
317  {
318  IScorable<Item, SourceScore> source = this.inner;
319  var sourceScore = source.GetScore(item, state);
320  var targetScore = this.selector(item, sourceScore);
321  return targetScore;
322  }
323  }
324 
325  public sealed class FirstScorable<Item, Score> : DelegatingFoldScorable<Item, Score>
326  {
327  public FirstScorable(IEnumerable<IScorable<Item, Score>> scorables)
328  : base(null, Comparer<Score>.Default, scorables)
329  {
330  }
331 
332  public override bool OnStageHandler(FoldStage stage, IScorable<Item, Score> scorable, Item item, object state, Score score)
333  {
334  switch (stage)
335  {
336  case FoldStage.AfterFold: return false;
337  case FoldStage.StartPost: return true;
338  case FoldStage.AfterPost: return false;
339  default: throw new NotImplementedException();
340  }
341  }
342  }
343 
344  public sealed class TraitsScorable<Item, Score> : DelegatingFoldScorable<Item, Score>
345  {
346  private readonly ITraits<Score> traits;
347 
348  public TraitsScorable(ITraits<Score> traits, IComparer<Score> comparer, IEnumerable<IScorable<Item, Score>> scorables)
349  : base(null, comparer, scorables)
350  {
351  SetField.NotNull(out this.traits, nameof(traits), traits);
352  }
353 
354  public override bool OnStageHandler(FoldStage stage, IScorable<Item, Score> scorable, Item item, object state, Score score)
355  {
356  switch (stage)
357  {
358  case FoldStage.AfterFold: return OnFold(scorable, item, state, score);
359  case FoldStage.StartPost: return true;
360  case FoldStage.AfterPost: return false;
361  default: throw new NotImplementedException();
362  }
363  }
364 
365  private bool OnFold(IScorable<Item, Score> scorable, Item item, object state, Score score)
366  {
367  if (this.comparer.Compare(score, this.traits.Minimum) < 0)
368  {
369  throw new ArgumentOutOfRangeException(nameof(score));
370  }
371 
372  var maximum = this.comparer.Compare(score, this.traits.Maximum);
373  if (maximum > 0)
374  {
375  throw new ArgumentOutOfRangeException(nameof(score));
376  }
377  else if (maximum == 0)
378  {
379  return false;
380  }
381 
382  return true;
383  }
384  }
385 }
delegate bool OnStageDelegate(FoldStage stage, IScorable< Item, Score > scorable, Item item, object state, Score score)
Event handler delegate for fold scorable stages.
static readonly Task< T > Null
Definition: Tasks.cs:46
override Score GetScore(OuterItem item, Token< InnerItem, Score > state)
Definition: Scorables.cs:299
SelectScoreScorable(IScorable< Item, SourceScore > scorable, Func< Item, SourceScore, TargetScore > selector)
Definition: Scorables.cs:310
override bool HasScore(Item item, object state)
Definition: Scorables.cs:260
Score GetScore(Item item, object state)
Gets the score for this item.
SelectItemScorable(IScorable< InnerItem, Score > scorable, Func< OuterItem, InnerItem > selector)
Definition: Scorables.cs:281
Namespace for models generated from the http://luis.ai REST API.
Definition: FormDialog.cs:837
Task< object > PrepareAsync(Item item, CancellationToken token)
Perform some asynchronous work to analyze the item and produce some opaque state. ...
Task DoneAsync(Item item, object state, CancellationToken token)
The scoring process has completed - dispose of any scoped resources.
FoldStage
The stage of the FoldScorable events.
Definition: FoldScorable.cs:47
Aggregates some non-empty set of inner scorables to produce an outer scorable.
override bool OnStageHandler(FoldStage stage, IScorable< Item, Score > scorable, Item item, object state, Score score)
Definition: Scorables.cs:354
Fold an aggregation of scorables to produce a winning scorable.
Definition: FoldScorable.cs:72
Task PostAsync(Item item, object state, CancellationToken token)
If this scorable wins, this method is called.
Namespace for internal scorable implementation that is not useful for most developers and may change ...
Namespace for scorable interfaces, classes and compositions.
Namespace for internal machinery that is not useful for most developers and may change in the future...
FirstScorable(IEnumerable< IScorable< Item, Score >> scorables)
Definition: Scorables.cs:327
override bool OnStageHandler(FoldStage stage, IScorable< Item, Score > scorable, Item item, object state, Score score)
Definition: Scorables.cs:332
static readonly IScorable< Item, Score > Instance
Definition: Scorables.cs:217
override async Task< Token< InnerItem, Score > > PrepareAsync(OuterItem sourceItem, CancellationToken token)
Definition: Scorables.cs:287
This scorable delegates the stage event handler to an external delegate or an overridable virtual met...
Provides the state to aggregate the state (and associated scorable) of multiple scorables.
TraitsScorable(ITraits< Score > traits, IComparer< Score > comparer, IEnumerable< IScorable< Item, Score >> scorables)
Definition: Scorables.cs:348
bool HasScore(Item item, object state)
Returns whether this scorable wants to participate in scoring this item.
IScorable< InnerItem, InnerScore > Scorable
Namespace for the internal fibers machinery that is not useful for most developers and may change in ...
Core namespace for Dialogs and associated infrastructure.
Namespace for the machinery needed to talk to http://luis.ai.
WhereScoreScorable(IScorable< Item, Score > scorable, Func< Item, Score, bool > predicate)
Definition: Scorables.cs:254
Root namespace for the Microsoft Bot Builder SDK.