summaryrefslogtreecommitdiff
path: root/src/continuations/library/scala/util/continuations/ControlContext.scala
blob: c196809da9aa5c9bb3d8ea940bf14317db877799 (plain) (blame)
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
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2010-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.util.continuations

import scala.annotation.{ Annotation, StaticAnnotation, TypeConstraint }

/** This annotation is used to mark a parameter as part of a continuation
 * context.
 *
 * The type `A @cpsParam[B,C]` is desugared to `ControlContext[A,B,C]` at compile
 * time.
 *
 * @tparam B  The type of computation state after computation has executed, and
 *   before control is returned to the shift.
 * @tparam C  The eventual return type of this delimited compuation.
 * @see scala.util.continuations.ControlContext
 */
class cpsParam[-B,+C] extends StaticAnnotation with TypeConstraint

private class cpsSym[B] extends Annotation // implementation detail

private class cpsSynth extends Annotation // implementation detail

private class cpsPlus extends StaticAnnotation with TypeConstraint // implementation detail
private class cpsMinus extends Annotation // implementation detail


/**
 * This class represent a portion of computation that has a 'hole' in it.  The
 * class has the ability to compute state up until a certain point where the
 * state has the `A` type.  If this context is given a function of type
 * `A => B` to move the state to the `B` type, then the entire computation can
 * be completed resulting in a value of type `C`.
 *
 * An Example: {{{
 *   val cc = new ControlContext[String, String, String](
 *      fun = { (f: String=>String, err: Exception => String) =>
 *        val updatedState =
 *          try f("State")
 *          catch {
 *            case e: Exception => err(e)
 *          }
 *        updatedState + "-Complete!"
 *      },
 *      x = null.asIntanceOf[String]
 *  }
 *  cc.foreach(_ + "-Continued")  // Results in "State-Continued-Complete!"
 * }}}
 *
 * This class is used to transform calls to `shift` in the `continuations`
 * package.  Direct use and instantiation is possible, but usually reserved
 * for advanced cases.
 *
 *
 * A context may either be ''trivial'' or ''non-trivial''.   A ''trivial''
 * context '''just''' has a state of type `A`.  When completing the computation,
 * it's only necessary to use the function of type `A => B` directly against
 * the trivial value. A ''non-trivial'' value stores a computation '''around'''
 * the state transformation of type `A => B` and cannot be short-circuited.
 *
 * @param fun The captured computation so far.  The type
 *   `(A => B, Exception => B) => C` is a function where:
 *   - The first parameter `A=>B` represents the computation defined against
 *       the current state held in the ControlContext.
 *   - The second parameter `Exception => B` represents a computation to
 *       perform if an exception is thrown from the first parameter's computation.
 *   - The return value is the result of the entire computation contained in this
 *       `ControlContext`.
 * @param x  The current state stored in this context.  Allowed to be null if
 *   the context is non-trivial.
 * @tparam A  The type of the state currently held in the context.
 * @tparam B  The type of the transformed state needed to complete this computation.
 * @tparam C  The return type of the entire computation stored in this context.
 * @note `fun` and `x` are allowed to be `null`.
 * @see scala.util.continutations.shiftR
 */
final class ControlContext[+A,-B,+C](val fun: (A => B, Exception => B) => C, val x: A) extends Serializable {

  /*
    final def map[A1](f: A => A1): ControlContext[A1,B,C] = {
      new ControlContext((k:(A1 => B)) => fun((x:A) => k(f(x))), null.asInstanceOf[A1])
    }

    final def flatMap[A1,B1<:B](f: (A => ControlContext[A1,B1,B])): ControlContext[A1,B1,C] = {
      new ControlContext((k:(A1 => B1)) => fun((x:A) => f(x).fun(k)))
    }
  */

  /**
   * Modifies the currently captured state in this `ControlContext`.
   * @tparam A1 The new type of state in this context.
   * @param f A transformation function on the current state of the `ControlContext`.
   * @return The new `ControlContext`.
   */
  @noinline final def map[A1](f: A => A1): ControlContext[A1,B,C] = {
    if (fun eq null)
      try {
        new ControlContext[A1,B,C](null, f(x)) // TODO: only alloc if f(x) != x
      } catch {
        case ex: Exception =>
          new ControlContext((k: A1 => B, thr: Exception => B) => thr(ex).asInstanceOf[C], null.asInstanceOf[A1])
      }
    else
      new ControlContext({ (k: A1 => B, thr: Exception => B) =>
        fun( { (x:A) =>
          var done = false
          try {
            val res = f(x)
            done = true
            k(res)
          } catch {
            case ex: Exception if !done =>
              thr(ex)
          }
        }, thr)
      }, null.asInstanceOf[A1])
  }


  // it would be nice if @inline would turn the trivial path into a tail call.
  // unfortunately it doesn't, so we do it ourselves in SelectiveCPSTransform

  /**
   * Maps and flattens this `ControlContext` with another `ControlContext` generated from the current state.
   * @note   The resulting comuptation is still the type `C`.
   * @tparam A1 The new type of the contained state.
   * @tparam B1 The new type of the state after the stored continuation has executed.
   * @tparam C1 The result type of the nested `ControlContext`.  Because the nested `ControlContext` is executed within
   *   the outer `ControlContext`, this type must `>: B` so that the resulting nested computation can be fed through
   *   the current continuation.
   * @param f A transformation function from the current state to a nested `ControlContext`.
   * @return The transformed `ControlContext`.
   */
  @noinline final def flatMap[A1,B1,C1<:B](f: (A => ControlContext[A1,B1,C1])): ControlContext[A1,B1,C] = {
    if (fun eq null)
      try {
        f(x).asInstanceOf[ControlContext[A1,B1,C]]
      } catch {
        case ex: Exception =>
          new ControlContext((k: A1 => B1, thr: Exception => B1) => thr(ex).asInstanceOf[C], null.asInstanceOf[A1])
      }
    else
      new ControlContext({ (k: A1 => B1, thr: Exception => B1) =>
        fun( { (x:A) =>
          var done = false
          try {
            val ctxR = f(x)
            done = true
            val res: C1 = ctxR.foreachFull(k, thr) // => B1
            res
          } catch {
            case ex: Exception if !done =>
              thr(ex).asInstanceOf[B] // => B NOTE: in general this is unsafe!
          }                           // However, the plugin will not generate offending code
        }, thr.asInstanceOf[Exception=>B]) // => B
      }, null.asInstanceOf[A1])
  }

  /**
   * Runs the computation against the state stored in this `ControlContext`.
   * @param f the computation that modifies the current state of the context.
   * @note This method could throw exceptions from the computations.
   */
  final def foreach(f: A => B) = foreachFull(f, throw _)

  def foreachFull(f: A => B, g: Exception => B): C = {
    if (fun eq null)
      f(x).asInstanceOf[C]
    else
      fun(f, g)
  }

  /** @return true if this context only stores a state value and not any deferred computation. */
  final def isTrivial = fun eq null
  /** @return The current state value. */
  final def getTrivialValue = x.asInstanceOf[A]

  // need filter or other functions?

  final def flatMapCatch[A1>:A,B1<:B,C1>:C<:B1](pf: PartialFunction[Exception, ControlContext[A1,B1,C1]]): ControlContext[A1,B1,C1] = { // called by codegen from SelectiveCPSTransform
    if (fun eq null)
      this
    else {
      val fun1 = (ret1: A1 => B1, thr1: Exception => B1) => {
        val thr: Exception => B1 = { t: Exception =>
          var captureExceptions = true
          try {
            if (pf.isDefinedAt(t)) {
              val cc1 = pf(t)
              captureExceptions = false
              cc1.foreachFull(ret1, thr1) // Throw => B
            } else {
              captureExceptions = false
              thr1(t) // Throw => B1
            }
          } catch {
            case t1: Exception if captureExceptions => thr1(t1) // => E2
          }
        }
        fun(ret1, thr)// fun(ret1, thr)  // => B
      }
      new ControlContext(fun1, null.asInstanceOf[A1])
    }
  }

  final def mapFinally(f: () => Unit): ControlContext[A,B,C] = { // called in code generated by SelectiveCPSTransform
    if (fun eq null) {
      try {
        f()
        this
      } catch {
        case ex: Exception =>
          new ControlContext((k: A => B, thr: Exception => B) => thr(ex).asInstanceOf[C], null.asInstanceOf[A])
      }
    } else {
      val fun1 = (ret1: A => B, thr1: Exception => B) => {
        val ret: A => B = { x: A =>
          var captureExceptions = true
          try {
            f()
            captureExceptions = false
            ret1(x)
          } catch {
            case t1: Exception if captureExceptions => thr1(t1)
          }
        }
        val thr: Exception => B = { t: Exception =>
          var captureExceptions = true
          try {
            f()
            captureExceptions = false
            thr1(t)
          } catch {
            case t1: Exception if captureExceptions => thr1(t1)
          }
        }
        fun(ret, thr1)
      }
      new ControlContext(fun1, null.asInstanceOf[A])
    }
  }

}