/
CpsLogicMonad.scala
427 lines (357 loc) · 11.3 KB
/
CpsLogicMonad.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
package cps.monads.logic
import cps.*
import scala.annotation.tailrec
import scala.util.*
/**
* Typeclass for monad with backtracking logic operations.
* We can interpret it as a monad with non-deterministic choice or as a
* potentialy infinite stream of values corresponding to possible choices.
*
* The default implementation is LogicStream (for no need in async operations)
* or LogicStreamT[F] (for need in async operations)
*
*
* @tparam M
*/
trait CpsLogicMonad[M[_]] extends CpsTryMonad[M] {
override type Context <: CpsLogicMonadContext[M]
/**
* Monad, which can be used to observe values of computation
* and can be used in map/flatMap/... operations.
* Usefull for cases, where we should mix logic and async operations.
* If implementation is LogicStream[F[_],A], that Observer is F[_].
*/
type Observer[A]
/**
* instance of observer cps monad.
*/
val observerCpsMonad: CpsTryMonad[Observer]
/**
* mzero in haskell LogicT
* Synonym for 'empty'.
*/
def mzero[A]: M[A]
/**
* empty solution set.
* (same as 'mzero')
*/
def empty[A]: M[A] = mzero
/**
* mplus in haskell LogicT
* Synonym for 'orElse'.
*/
def mplus[A](a: M[A], b: => M[A]): M[A]
/**
* Create a computation, which explore <code> a <code> and then <code> b </code>
* end do not start evaluation of <code> b </code> until all values or <code> a </code> are explored.
*
* Synonym for MonadPlus 'mplus' or Alternative '<|>' in haskell.
*/
def seqOr[A](a: M[A], b: => M[A]): M[A] = mplus(a,b)
/**
* Split computation into part, which return as first value and rest of computation
* and return this as logic stream.
* @param c
* @tparam A
* @return
*/
def msplit[A](c: M[A]): M[Option[(Try[A], M[A])]]
/**
* Split computation into part, which return as first value and rest of computation
* and return this as observer.
* @param c
* @tparam A
* @return
*/
def fsplit[A](c: M[A]): Observer[Option[(Try[A], M[A])]]
def unsplit[A](ta: Try[A], m:M[A]): M[A] =
mplus(fromTry(ta), m)
/**
* Flatten observer into computation.
* @param fma - observer to flatten
* @return - computation, which adopt observer wrapper
*/
def flattenObserver[A](fma: Observer[M[A]]): M[A]
/**
* Can be viewed as `fair Or` -- values from both computations are interleaved
*/
def interleave[A](a: M[A], b: M[A]): M[A] = {
flatMap(msplit(a)) { sa =>
sa match
case None => b
case Some((ta, sa1)) =>
mplus(fromTry(ta), interleave(b, sa1))
}
}
/**
* Can be viewed as `fair And` -- flatMap results are mixed over the all choices in <code> ma </code>
*
* >>- in haskell LogicT
*/
def fairFlatMap[A, B](ma: M[A], mb: A => M[B]): M[B] = {
flatMap(msplit(ma)) { sa =>
sa match
case None => mzero
case Some((ta, sa1)) =>
ta match
case Success(a) =>
interleave(mb(a), fairFlatMap(sa1, mb))
case Failure(ex) =>
error(ex)
}
}
/**
* ifte -- if/then/else which works not on boolean condition, but on
* existence of values in computation.
* @param a - computation to check
* @param thenp - what to do after <code> a </code>
* @param elsep - what to do if <code> a </code> is empty
*/
def ifte[A, B](a: M[A], thenp: A => M[B], elsep: => M[B]): M[B] = {
flatMap(msplit(a)) { sc =>
sc match
case None => elsep
case Some((ta, sa)) =>
ta match
case Success(a) =>
mplus(thenp(a), flatMap(sa)(thenp))
case Failure(ex) =>
error(ex)
}
}
/**
* get the first value of computation, discarding all other.
* head of the list, or soft cut in prolog (scoped inside current computation).
* @param a
* @tparam A
* @return
*/
def once[A](a: M[A]): M[A] = {
flatMap(msplit(a)) { sc =>
sc match
case None => mzero
case Some((ta, sa)) =>
fromTry(ta)
}
}
def mObserveOne[A](ma:M[A]): Observer[Option[A]]
def mObserveN[A](ma: M[A], n: Int): Observer[IndexedSeq[A]] =
mFoldLeftWhile(ma,IndexedSeq.empty[A], (seq:IndexedSeq[A]) => seq.size < n) { (seq,a) =>
seq :+ a
}
def mFoldLeftWhileM[A,B](ma:M[A], zero: Observer[B], p: B=>Boolean)(op: (Observer[B],Observer[A])=>Observer[B]): Observer[B]
def mFoldLeftWhile[A,B](ma:M[A], zero: B, p: B=>Boolean)(op: (B,A)=>B): Observer[B] = {
mFoldLeftWhileM(ma, observerCpsMonad.pure(zero), p) { (bObs,aObs) =>
observerCpsMonad.flatMap(bObs) { b =>
observerCpsMonad.flatMap(aObs) { a =>
observerCpsMonad.pure(op(b,a))
}
}
}
}
def fromCollection[A](collect: IterableOnce[A]): M[A] = {
def fromIt(it: Iterator[A]): M[A] =
if (it.hasNext) {
mplus(pure(it.next()), fromIt(it))
} else {
mzero
}
fromIt(collect.iterator)
}
}
object CpsLogicMonad {
type Aux[M[+_],F[_]] = CpsLogicMonad[M] {
type Observer[T] = F[T]
}
}
trait CpsLogicMonadContext[M[_]] extends CpsTryMonadContext[M] {
override def monad: CpsLogicMonad[M]
}
class CpsLogicMonadInstanceContextBody[M[_]](m:CpsLogicMonad[M]) extends CpsLogicMonadContext[M] {
override def monad: CpsLogicMonad[M] = m
}
trait CpsLogicMonadInstanceContext[M[_]] extends CpsLogicMonad[M] {
override type Context = CpsLogicMonadInstanceContextBody[M]
override def apply[T](op: CpsLogicMonadInstanceContextBody[M] => M[T]): M[T] = {
op(new CpsLogicMonadInstanceContextBody[M](this))
}
}
/**
* Transform collection into logical stream, which include all elements.
* @param collection - collection to transform
* @param m - logical monad to use.
*/
def all[M[_],A](collection: IterableOnce[A])(using m:CpsLogicMonad[M]): M[A] =
def allIt(it: Iterator[A]): M[A] =
if (it.hasNext) {
m.mplus(m.pure(it.next()), allIt(it))
} else {
m.mzero
}
allIt(collection.iterator)
/**
* CpsLogicMonad extension methods.
*/
extension [M[_],A](ma: M[A])(using m:CpsLogicMonad[M])
/**
* filter values, which satisfy predicate.
*/
def filter(p: A => Boolean): M[A] =
m.flatMap(m.msplit(ma)) { sc =>
sc match
case None => m.mzero
case Some((ta, sa)) =>
ta match
case Success(a) =>
if (p(a)) {
m.mplus(m.pure(a), sa.filter(p))
} else {
sa.filter(p)
}
case Failure(ex) =>
m.error(ex)
}
/**
* get first N values of computation, discarding all other.
* @param n - how many values to get
* @return - sequence of values in observer monad
* @see cps.monads.logic.CpsLogicMonad.mObserveN
*/
def observeN(n: Int): m.Observer[IndexedSeq[A]] =
m.mObserveN(ma,n)
/**
* get first value of computation.
* @see cps.monads.logic.CpsLogicMonad.mObserveOne
*/
def observeOne: m.Observer[Option[A]] =
m.mObserveOne(ma)
// avoid publish methid which can cause infinite loop
//def observeAll: m.Observer[Seq[A]] =
// m.mObserveAll(ma)
/**
* Synonym for 'mplus'.
* @param mb - computation to add
* @return - stream, which contains values from <code> ma </code> and when <code> ma </code> is exhaused - <code> mb </code>
* @see cps.monads.logic.CpsLogicMonad.mplus
*/
def |+|(mb: =>M[A]): M[A] =
m.mplus(ma,mb)
/**
* Synonym for 'mplus'.
* @see cps.monads.logic.CpsLogicMonad.mplus
*/
def ||(mb: =>M[A]): M[A] =
m.mplus(ma,mb)
/**
* interleave current computation with <code> mb </code>
* @param mb computation to interleave.
* @return - stream, which contains values from <code> ma </code> and <code> mb </code> in interleaved order.
* @see cps.monads.logic.CpsLogicMonad.interleave
*/
def |(mb: =>M[A]): M[A] =
m.interleave(ma,mb)
/**
* Synonym for 'fairFlatMap' or haskell >>-
* @param f
* @tparam B
* @return - stream, which contains values from <code> ma </code> and <code> f </code> applied to each value of <code> ma </code>
* in interleaved order.
* @see cps.monads.logic.CpsLogicMonad.fairFlatMap
*/
def &>>[B](f: A=>M[B]): M[B] =
m.fairFlatMap(ma,f)
/**
* Version of flatMap, which interleave all results of ma
* (i.e. horizontal search instead of bfs).
* @param f - function to apply to each value of <code> ma </code>
* @see cps.monads.logic.CpsLogicMonad.fairFlatMap
*/
def fairFlatMap[B](f: A=>M[B]): M[B] =
m.fairFlatMap(ma,f)
/**
* retrieve only first value of computation.
* @return - stream, which contains only first value of <code> ma </code>
* @see cps.monads.logic.CpsLogicMonad.once
*/
def once: M[A] =
m.once(ma)
/**
* If <code> ma </code> is note empty, then run <code> thenp </code> on it else <code> elsep </code>,
* @param thenp
* @param elsep
* @tparam B
* @return
* @see cps.monads.logic.CpsLogicMonad.ifte
*/
def ifThenElseM[B](thenp: A => M[B])(elsep: => M[B]): M[B] =
m.ifte(ma, thenp, elsep)
transparent inline def ifThenElse[B](inline thenp: A => B)(inline elsep: => B): M[B] =
m.ifte(ma, a => reify(thenp(a)), reify(elsep))
/**
* Run <code> thenp </code> if <code> ma </code> is empty.
* @param thenp
* @return
* @see cps.monads.logic.CpsLogicMonad.otherwise
*/
def otherwise(thenp: =>M[A]): M[A] =
m.ifte(ma, (a:A) => m.pure(a), thenp)
/**
* Should be used inside of reify block over CpsLogicMonad.
* The next sequent code will be executed only if <code> p </code> is true,
* otherwise the computation of the current value of logical stream will be terminated.
* @param p - predicate to check
* @param mc - monad context
* @tparam M
*/
transparent inline def guard[M[_]](p: =>Boolean)(using mc:CpsLogicMonadContext[M]): Unit =
reflect{
if (p) mc.monad.pure(()) else mc.monad.mzero
}
/**
* Should be used inside of reify block over CpsLogicMonad.
* The next sequent code will be executed for all elements of <code> collection </code>
* @param collection - collection to iterate over
* @param mc - monad context
* @tparam M
*/
transparent inline def choicesFrom[M[_],A](collection: IterableOnce[A])(using mc:CpsLogicMonadContext[M]): A =
reflect{
mc.monad.fromCollection(collection)
}
/**
* Should be used inside of reify block over CpsLogicMonad.
* Form mini-DSL after <code>choice</code> prefux
* @param mc
* @tparam M
*/
class Choices[M[_]](using mc:CpsLogicMonadContext[M]) {
/**
* Should be used inside of reify block over CpsLogicMonad.
* The next sequent code will be executed for all <code> values </code>
*
* @param values - values to iterate over
* @param mc - monad context
* @tparam M
*/
transparent inline def apply[A](values: A*): A =
reflect {
mc.monad.fromCollection(values)
}
transparent inline def from[A](values: IterableOnce[A]): A =
reflect {
mc.monad.fromCollection(values)
}
transparent inline def empty[A]: A =
reflect {
mc.monad.empty[A]
}
}
/**
* Should be used inside of reify block over CpsLogicMonad.
* Create a Choices instance, which can be used for 'injecting'
* value of collection into logical stream via mini-DSL.
* @param mc - monad context
* @tparam M
*/
def choices[M[_]](using mc:CpsLogicMonadContext[M]): Choices[M] =
new Choices[M]()