dvcompute_cons/simulation/composite/
mod.rs

1// Copyright (c) 2020-2022  David Sorokin <davsor@mail.ru>, based in Yoshkar-Ola, Russia
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7use std::marker::PhantomData;
8
9use crate::simulation;
10use crate::simulation::Point;
11use crate::simulation::event::*;
12use crate::simulation::observable::disposable::*;
13
14/// Return a new `Composite` computation by the specified pure value.
15#[inline]
16pub fn return_composite<T>(val: T) -> Return<T> {
17    Return { val: val }
18}
19
20/// Delay the `Composite` computation.
21#[inline]
22pub fn delay_composite<F, M>(f: F) -> Delay<F, M>
23    where F: FnOnce() -> M,
24          M: Composite
25{
26    Delay { f: f, _phantom: PhantomData }
27}
28
29/// Construct a new `Composite` computation by the specified function.
30#[inline]
31pub fn cons_composite<F, T>(f: F) -> Cons<F, T>
32    where F: FnOnce(DisposableBox, &Point) -> simulation::Result<(T, DisposableBox)>
33{
34     Cons { f: f, _phantom: PhantomData }
35}
36
37/// Allows embedding arbitrary `Disposable` actions in `Composite` computation.
38#[inline]
39pub fn disposable_composite<D>(action: D) -> DisposableComposite<D>
40    where D: Disposable + 'static
41{
42    DisposableComposite { action: action }
43}
44
45/// Create a sequence of computations.
46#[inline]
47pub fn composite_sequence<I, M>(comps: I) -> Sequence<I::IntoIter, M>
48    where I: IntoIterator<Item = M>,
49          M: Composite
50{
51    Sequence { comps: comps.into_iter(), _phantom: PhantomData }
52}
53
54/// Create a sequence of computations, where the result is ignored.
55#[inline]
56pub fn composite_sequence_<I, M>(comps: I) -> Sequence_<I::IntoIter, M>
57    where I: IntoIterator<Item = M>,
58          M: Composite
59{
60    Sequence_ { comps: comps.into_iter(), _phantom: PhantomData }
61}
62
63/// Trace the computation.
64#[inline]
65pub fn trace_composite<M>(msg: String, comp: M) -> Trace<M>
66    where M: Composite
67{
68    Trace { comp: comp, msg: msg}
69}
70
71/// The computation synchronized with the composite queue.
72pub trait Composite {
73
74    /// The type of the item that is returned in the current modeling time.
75    type Item;
76
77    /// Call the `Composite` computation in the current modeling time by updating the `Disposable` object.
78    #[doc(hidden)]
79    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(Self::Item, DisposableBox)>;
80
81    /// Bind the current computation with its continuation within the resulting computation.
82    #[inline]
83    fn and_then<U, F>(self, f: F) -> AndThen<Self, U, F>
84        where Self: Sized,
85              U: Composite,
86              F: FnOnce(Self::Item) -> U,
87    {
88        AndThen { comp: self, f: f, _phantom: PhantomData }
89    }
90
91    /// Map the current computation using the specified transform.
92    #[inline]
93    fn map<B, F>(self, f: F) -> Map<Self, B, F>
94        where Self: Sized,
95              F: FnOnce(Self::Item) -> B,
96    {
97        Map { comp: self, f: f, _phantom: PhantomData }
98    }
99
100    /// Zip the current computation with another one within the resulting computation.
101    #[inline]
102    fn zip<U>(self, other: U) -> Zip<Self, U>
103        where Self: Sized,
104              U: Composite
105    {
106        Zip { comp: self, other: other }
107    }
108
109    /// The function application.
110    #[inline]
111    fn ap<U, B>(self, other: U) -> Ap<Self, U, B>
112        where Self: Sized,
113              Self::Item: FnOnce(U::Item) -> B,
114              U: Composite
115    {
116        Ap { comp: self, other: other, _phantom: PhantomData }
117    }
118
119    /// Convert into a boxed value.
120    #[inline]
121    fn into_boxed(self) -> CompositeBox<Self::Item>
122        where Self: Sized + 'static
123    {
124        CompositeBox::new(move |disposable, p: &Point| { self.call_composite(disposable, p) })
125    }
126
127    /// Run the computation by the specified initial `Disposable` object.
128    #[inline]
129    fn run<D>(self, disposable: D) -> Run<Self, D>
130        where Self: Sized,
131              D: Disposable + 'static
132    {
133        Run { comp: self, disposable: disposable }
134    }
135
136    /// Run the computation by ignoring `Disposable` objects.
137    #[inline]
138    fn run_(self) -> Run_<Self>
139        where Self: Sized
140    {
141        Run_ { comp: self }
142    }
143}
144
145/// Allows converting to `Composite` computations.
146pub trait IntoComposite {
147
148    /// The target computation.
149    type Composite: Composite<Item = Self::Item>;
150
151    /// The type of item that is returned by the computation.
152    type Item;
153
154    /// Convert to the `Composite` computation.
155    fn into_composite(self) -> Self::Composite;
156}
157
158impl<M: Composite> IntoComposite for M {
159
160    type Composite = M;
161
162    type Item = M::Item;
163
164    #[inline]
165    fn into_composite(self) -> Self::Composite {
166        self
167    }
168}
169
170/// It represents the boxed `Composite` computation.
171#[must_use = "computations are lazy and do nothing unless to be run"]
172pub struct CompositeBox<T> {
173    f: Box<dyn CompositeFnBox<T>>
174}
175
176impl<T> CompositeBox<T> {
177
178    /// Create a new boxed computation.
179    #[doc(hidden)]
180    #[inline]
181    fn new<F>(f: F) -> Self
182        where F: FnOnce(DisposableBox, &Point) -> simulation::Result<(T, DisposableBox)> + 'static
183    {
184        CompositeBox {
185            f: Box::new(f)
186        }
187    }
188
189    /// Call the boxed function.
190    #[doc(hidden)]
191    #[inline]
192    pub fn call_box(self, arg: (DisposableBox, &Point,)) -> simulation::Result<(T, DisposableBox)> {
193        let CompositeBox { f } = self;
194        f.call_box(arg)
195    }
196}
197
198impl<T> Composite for CompositeBox<T> {
199
200    type Item = T;
201
202    #[inline]
203    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(Self::Item, DisposableBox)> {
204        self.call_box((disposable, p,))
205    }
206
207    #[inline]
208    fn into_boxed(self) -> CompositeBox<Self::Item>
209        where Self: Sized + 'static
210    {
211        self
212    }
213}
214
215/// A trait to support the stable version of Rust, where there is no `FnBox`.
216trait CompositeFnBox<T> {
217
218    /// Call the corresponding function.
219    fn call_box(self: Box<Self>, args: (DisposableBox, &Point,)) -> simulation::Result<(T, DisposableBox)>;
220}
221
222impl<T, F> CompositeFnBox<T> for F
223    where F: for<'a> FnOnce(DisposableBox, &'a Point) -> simulation::Result<(T, DisposableBox)>
224{
225    fn call_box(self: Box<Self>, args: (DisposableBox, &Point,)) -> simulation::Result<(T, DisposableBox)> {
226        let this: Self = *self;
227        this(args.0, args.1)
228    }
229}
230
231/// Allows creating the `Composite` computation from a pure value.
232#[must_use = "computations are lazy and do nothing unless to be run"]
233#[derive(Clone)]
234pub struct Return<T> {
235
236    /// Return a pure value, which is then transformed to the computation.
237    val: T
238}
239
240impl<T> Composite for Return<T> {
241
242    type Item = T;
243
244    #[doc(hidden)]
245    #[inline]
246    fn call_composite(self, disposable: DisposableBox, _: &Point) -> simulation::Result<(T, DisposableBox)> {
247        let Return { val } = self;
248        Result::Ok((val, disposable))
249    }
250}
251
252/// Allows delaying the `Composite` computation by the specified function.
253#[must_use = "computations are lazy and do nothing unless to be run"]
254#[derive(Clone)]
255pub struct Delay<F, M> {
256
257    /// Return the computation.
258    f: F,
259
260    /// To keep the type parameter.
261    _phantom: PhantomData<M>
262}
263
264impl<F, M> Composite for Delay<F, M>
265    where F: FnOnce() -> M,
266          M: Composite
267{
268    type Item = M::Item;
269
270    #[doc(hidden)]
271    #[inline]
272    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(M::Item, DisposableBox)> {
273        let Delay { f, _phantom } = self;
274        f().call_composite(disposable, p)
275    }
276}
277
278/// Allows constructing the `Composite` computation by the specified function.
279#[must_use = "computations are lazy and do nothing unless to be run"]
280#[derive(Clone)]
281pub struct Cons<F, T> {
282
283    /// The function of time point.
284    f: F,
285
286    /// To keep the type parameter.
287    _phantom: PhantomData<T>
288}
289
290impl<F, T> Composite for Cons<F, T>
291    where F: FnOnce(DisposableBox, &Point) -> simulation::Result<(T, DisposableBox)>
292{
293    type Item = T;
294
295    #[doc(hidden)]
296    #[inline]
297    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(T, DisposableBox)> {
298        let Cons { f, _phantom } = self;
299        f(disposable, p)
300    }
301}
302
303/// The monadic bind for the `Composite` computation.
304#[must_use = "computations are lazy and do nothing unless to be run"]
305#[derive(Clone)]
306pub struct AndThen<M, U, F> {
307
308    /// The current computation.
309    comp: M,
310
311    /// The continuation of the current computation.
312    f: F,
313
314    /// To keep the type parameter.
315    _phantom: PhantomData<U>
316}
317
318impl<M, U, F> Composite for AndThen<M, U, F>
319    where M: Composite,
320          U: Composite,
321          F: FnOnce(M::Item) -> U,
322{
323    type Item = U::Item;
324
325    #[doc(hidden)]
326    #[inline]
327    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(U::Item, DisposableBox)> {
328        let AndThen { comp, f, _phantom } = self;
329        match comp.call_composite(disposable, p) {
330            Result::Ok((a, disposable)) => {
331                let m = f(a);
332                m.call_composite(disposable, p)
333            },
334            Result::Err(e) => {
335                Result::Err(e)
336            }
337        }
338    }
339}
340
341/// The functor for the `Composite` computation.
342#[must_use = "computations are lazy and do nothing unless to be run"]
343#[derive(Clone)]
344pub struct Map<M, B, F> {
345
346    /// The current computation.
347    comp: M,
348
349    /// The transform.
350    f: F,
351
352    /// To keep the type parameter.
353    _phantom: PhantomData<B>
354}
355
356impl<M, B, F> Composite for Map<M, B, F>
357    where M: Composite,
358          F: FnOnce(M::Item) -> B,
359{
360    type Item = B;
361
362    #[doc(hidden)]
363    #[inline]
364    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(B, DisposableBox)> {
365        let Map { comp, f, _phantom } = self;
366        match comp.call_composite(disposable, p) {
367            Result::Ok((a, disposable)) => Result::Ok((f(a), disposable)),
368            Result::Err(e) => Result::Err(e)
369        }
370    }
371}
372
373/// The zip of two `Composite` computations.
374#[must_use = "computations are lazy and do nothing unless to be run"]
375#[derive(Clone)]
376pub struct Zip<M, U> {
377
378    /// The current computation.
379    comp: M,
380
381    /// Another computation.
382    other: U,
383}
384
385impl<M, U> Composite for Zip<M, U>
386    where M: Composite,
387          U: Composite
388{
389    type Item = (M::Item, U::Item);
390
391    #[doc(hidden)]
392    #[inline]
393    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<((M::Item, U::Item), DisposableBox)> {
394        let Zip { comp, other } = self;
395        comp.and_then(move |a| {
396            other.map(move |b| (a, b))
397        }).call_composite(disposable, p)
398    }
399}
400
401/// The function application for the `Composite` computation.
402#[must_use = "computations are lazy and do nothing unless to be run"]
403#[derive(Clone)]
404pub struct Ap<M, U, B> {
405
406    /// The current computation.
407    comp: M,
408
409    /// The continuation of the current computation.
410    other: U,
411
412    /// To keep the type parameter.
413    _phantom: PhantomData<B>
414}
415
416impl<M, U, B> Composite for Ap<M, U, B>
417    where M: Composite,
418          U: Composite,
419          M::Item: FnOnce(U::Item) -> B,
420{
421    type Item = B;
422
423    #[doc(hidden)]
424    #[inline]
425    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(B, DisposableBox)> {
426        let Ap { comp, other, _phantom } = self;
427        comp.and_then(move |f| {
428            other.map(move |a| { f(a) })
429        }).call_composite(disposable, p)
430    }
431}
432
433/// The sequence of computations.
434#[must_use = "computations are lazy and do nothing unless to be run"]
435#[derive(Clone)]
436pub struct Sequence<I, M> {
437
438    /// The computations.
439    comps: I,
440
441    /// To keep the type parameter.
442    _phantom: PhantomData<M>
443}
444
445impl<I, M> Composite for Sequence<I, M>
446    where I: Iterator<Item = M>,
447          M: Composite
448{
449    type Item = Vec<M::Item>;
450
451    #[doc(hidden)]
452    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(Self::Item, DisposableBox)> {
453        let Sequence { comps, _phantom } = self;
454        let mut v = {
455            match comps.size_hint() {
456                (_, Some(n)) => Vec::with_capacity(n),
457                (_, None) => Vec::new()
458            }
459        };
460        let mut disposable = disposable;
461        for m in comps {
462            match m.call_composite(disposable, p) {
463                Result::Ok((a, x)) => {
464                    v.push(a);
465                    disposable = x;
466                },
467                Result::Err(e) => {
468                    return Result::Err(e)
469                }
470            }
471        }
472        Result::Ok((v, disposable))
473    }
474}
475
476/// The sequence of computations with ignored result.
477#[must_use = "computations are lazy and do nothing unless to be run"]
478#[derive(Clone)]
479pub struct Sequence_<I, M> {
480
481    /// The computations.
482    comps: I,
483
484    /// To keep the type parameter.
485    _phantom: PhantomData<M>
486}
487
488impl<I, M> Composite for Sequence_<I, M>
489    where I: Iterator<Item = M>,
490          M: Composite
491{
492    type Item = ();
493
494    #[doc(hidden)]
495    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(Self::Item, DisposableBox)> {
496        let Sequence_ { comps, _phantom } = self;
497        let mut disposable = disposable;
498        for m in comps {
499            match m.call_composite(disposable, p) {
500                Result::Ok((_, x)) => { disposable = x; },
501                Result::Err(e) => return Result::Err(e)
502            }
503        }
504        Result::Ok(((), disposable))
505    }
506}
507
508/// The run function for `Composite` computation.
509#[must_use = "computations are lazy and do nothing unless to be run"]
510#[derive(Clone)]
511pub struct Run<M, D> {
512
513    /// The computation.
514    comp: M,
515
516    /// The initial `Disposable` action.
517    disposable: D
518}
519
520impl<M, D> Event for Run<M, D>
521    where M: Composite,
522          D: Disposable + 'static
523{
524    type Item = (M::Item, DisposableBox);
525
526    #[doc(hidden)]
527    fn call_event(self, p: &Point) -> simulation::Result<Self::Item> {
528        let Run { comp, disposable } = self;
529        let disposable = disposable.into_boxed();
530        comp.call_composite(disposable, p)
531    }
532}
533
534/// Another version of the run function for `Composite` computation.
535#[must_use = "computations are lazy and do nothing unless to be run"]
536#[derive(Clone)]
537pub struct Run_<M> {
538
539    /// The computation.
540    comp: M
541}
542
543impl<M> Event for Run_<M>
544    where M: Composite
545{
546    type Item = M::Item;
547
548    #[doc(hidden)]
549    fn call_event(self, p: &Point) -> simulation::Result<Self::Item> {
550        let Run_ { comp } = self;
551        let disposable = empty_disposable().into_boxed();
552        let (a, _) = comp.call_composite(disposable, p)?;
553        Result::Ok(a)
554    }
555}
556
557/// Computation that allows embedding `Disposable` actions.
558#[must_use = "computations are lazy and do nothing unless to be run"]
559#[derive(Clone)]
560pub struct DisposableComposite<D> {
561
562    /// The action.
563    action: D
564}
565
566impl<D> Composite for DisposableComposite<D>
567    where D: Disposable + 'static
568{
569    type Item = ();
570
571    #[doc(hidden)]
572    fn call_composite(self, disposable: DisposableBox, _p: &Point) -> simulation::Result<(Self::Item, DisposableBox)> {
573        let DisposableComposite { action } = self;
574        let disposable = disposable.merge(action).into_boxed();
575        Result::Ok(((), disposable))
576    }
577}
578
579/// Trace the computation.
580#[must_use = "computations are lazy and do nothing unless to be run"]
581#[derive(Clone)]
582pub struct Trace<M> {
583
584    /// The computation.
585    comp: M,
586
587    /// The message to print.
588    msg: String
589}
590
591impl<M> Composite for Trace<M>
592    where M: Composite
593{
594    type Item = M::Item;
595
596    #[doc(hidden)]
597    #[inline]
598    fn call_composite(self, disposable: DisposableBox, p: &Point) -> simulation::Result<(Self::Item, DisposableBox)> {
599        let Trace { comp, msg } = self;
600        p.trace(&msg);
601        comp.call_composite(disposable, p)
602    }
603}