├── .gitignore ├── .scalafmt.conf ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── build.sbt ├── deploy.sbt.disabled ├── project ├── build.properties └── plugins.sbt ├── src └── main │ └── scala │ └── com │ └── qifun │ └── statelessFuture │ ├── ANormalForm.scala │ ├── Awaitable.scala │ ├── AwaitableFactory.scala │ └── package.scala └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | *.swp 4 | 5 | # sbt specific 6 | .cache 7 | .history/ 8 | .lib/ 9 | dist/* 10 | target/ 11 | lib_managed/ 12 | src_managed/ 13 | project/boot/ 14 | project/plugins/project/ 15 | 16 | # Scala-IDE specific 17 | .scala_dependencies 18 | .worksheet 19 | .classpath 20 | .project 21 | .settings/ 22 | target/ 23 | local.sbt 24 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | maxColumn = 120 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | addons: 4 | apt: 5 | packages: 6 | - graphviz 7 | 8 | language: scala 9 | 10 | jdk: 11 | - oraclejdk8 12 | 13 | before_cache: 14 | - find $HOME/.sbt -name '*.lock' -delete 15 | - find $HOME/.ivy2 -name 'ivydata-*.properties' -delete 16 | 17 | cache: 18 | directories: 19 | - $HOME/.ivy2/cache 20 | - $HOME/.sbt/boot/ 21 | 22 | script: 23 | - sbt +test 24 | 25 | before_deploy: 26 | 27 | deploy: 28 | skip_cleanup: true 29 | provider: script 30 | script: sbt "release with-defaults" 31 | on: 32 | condition: -e ./deploy.sbt 33 | all_branches: true 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | stateless-future 2 | Copyright 2014 深圳岂凡网络有限公司 (Shenzhen QiFun Network Corp., LTD) 3 | 4 | Author: 杨博 (Yang Bo) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Stateless Future 2 | ================ 3 | 4 | [![Join the chat at https://gitter.im/Atry/stateless-future](https://badges.gitter.im/Atry/stateless-future.svg)](https://gitter.im/Atry/stateless-future?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![Build Status](https://travis-ci.org/Atry/stateless-future.svg?branch=master)](https://travis-ci.org/Atry/stateless-future) 6 | 7 | **Stateless Future** is a set of Domain-specific language for asynchronous programming, in the pure functional flavor. 8 | 9 | Stateless Futures provide similar API to `scala.concurrent.Future` and [scala.async](http://docs.scala-lang.org/sips/pending/async.html), except Stateless Futures are simpler, cleaner, and more powerful than `scala.concurrent.Future` and `scala.async`. 10 | 11 | There was a [continuation plugin](http://www.scala-lang.org/old/node/2096) for Scala. The continuation plugin also provided a DSL to define control flows like `stateless-future` or `scala.async`. I created the following table to compare the three DSL: 12 | 13 | | | stateless-future | scala.concurrent.Future and scala.async | scala.util.continuations | 14 | | ------------- | ---------------- | --------------------------------------- | ------------------------ | 15 | | Stateless | Yes | No | Yes | 16 | | Threading-free | Yes | No | Yes | 17 | | Exception handling in "A-Normal Form" | Yes | No | No | 18 | | Tail call optimization in "A-Normal Form" | Yes | No | No | 19 | | Pattern matching in "A-Normal Form" | Yes | Yes | Yes, but buggy | 20 | | Lazy val in "A-Normal Form" | No, because of [some underlying scala.reflect bugs](https://issues.scala-lang.org/browse/SI-8499) | Only for those contain no `await` | Yes, but buggy | 21 | 22 | ## Usage 23 | 24 | ### Create a Stateless Future 25 | 26 | import com.qifun.statelessFuture.Future 27 | val randomDoubleFuture: Future.Stateless[Double] = Future { 28 | println("Generating a random Double...") 29 | math.random() 30 | } 31 | 32 | A Stateless Future instance is lazy, only evaluated when you query it. Thus there is nothing printed when you create the Stateless Future. 33 | 34 | 35 | ### Read from a Stateless Future 36 | 37 | println("I am going to read a random Double.") 38 | for (randomDouble <- randomDoubleFuture) { 39 | println(s"Recevied $randomDouble.") 40 | } 41 | 42 | Output: 43 | 44 | I am going to read a random Double. 45 | Generating a random Double... 46 | Recevied 0.19722960355012198. 47 | 48 | ### Another Stateless Future that invokes the former Stateless Future twice. 49 | 50 | val anotherFuture = Future { 51 | println("I am going to read the first random Double.") 52 | val randomDouble1 = randomDoubleFuture.await 53 | println(s"The first random Double is $randomDouble1.") 54 | 55 | println("I am going to read the second random Double.") 56 | val randomDouble2 = randomDoubleFuture.await 57 | println(s"The second random Double is $randomDouble2.") 58 | } 59 | 60 | println("Before running the Future.") 61 | for (unit <- anotherFuture) { 62 | println("After running the Future.") 63 | } 64 | 65 | Output: 66 | 67 | Before running the Future. 68 | I am going to read the first random Double. 69 | Generating a random Double... 70 | The first random Double is 0.10768210465170625. 71 | I am going to read the second random Double. 72 | Generating a random Double... 73 | The second random Double is 0.6134780449033244. 74 | After running the Future. 75 | 76 | Note the magic `await` postfix, which invokes the the former Stateless Future `randomDoubleFuture`. It looks like a normal Scala method calls, but does not block any thread. 77 | 78 | ### A complex example with control structures 79 | 80 | import scala.concurrent.duration._ 81 | import scala.util.control.Exception.Catcher 82 | import com.qifun.statelessFuture.Future 83 | 84 | val executor = java.util.concurrent.Executors.newSingleThreadScheduledExecutor 85 | 86 | // Manually implements a Stateless Future, which is the asynchronous version of `Thread.sleep()` 87 | def asyncSleep(duration: Duration) = new Future.Stateless[Unit] { 88 | import scala.util.control.TailCalls._ 89 | def onComplete(handler: Unit => TailRec[Unit])(implicit catcher: Catcher[TailRec[Unit]]) = { 90 | executor.schedule(new Runnable { 91 | def run() { 92 | handler().result 93 | } 94 | }, duration.length, duration.unit) 95 | done() 96 | } 97 | } 98 | 99 | // Without the keyword `new`, you have the magic version of `Future` constructor, 100 | // which enables the magic postfix `await`. 101 | val sleep10seconds = Future { 102 | var i = 0 103 | while (i < 10) { 104 | println(s"I have slept $i times.") 105 | // The magic postfix `await` invokes the asynchronous method `asyncSleep`. 106 | // It looks like normal `Thread.sleep()`, but does not block any thread. 107 | asyncSleep(1.seconds).await 108 | i += 1 109 | } 110 | i 111 | } 112 | 113 | // When `sleep10seconds` is running, it could report failures to this catcher 114 | implicit def catcher: Catcher[Unit] = { 115 | case e: Exception => { 116 | println("An exception occured when I was sleeping: " + e.getMessage) 117 | } 118 | } 119 | 120 | // A Stateless Future instance is lazy, only evaluating when you query it. 121 | println("Before the evaluation of the Stateless Future `sleep10seconds`.") 122 | for (total <- sleep10seconds) { 123 | println("After the evaluation of the Stateless Future `sleep10seconds`.") 124 | println(s"I slept $total times in total.") 125 | executor.shutdown() 126 | } 127 | 128 | 129 | Run it and you will see the output: 130 | 131 | Before evaluation of the Stateless Future `sleep10seconds`. 132 | I have slept 0 times. 133 | I have slept 1 times. 134 | I have slept 2 times. 135 | I have slept 3 times. 136 | I have slept 4 times. 137 | I have slept 5 times. 138 | I have slept 6 times. 139 | I have slept 7 times. 140 | I have slept 8 times. 141 | I have slept 9 times. 142 | After evaluation of the Stateless Future `sleep10seconds`. 143 | I slept 10 times in total. 144 | 145 | ## Further Information 146 | 147 | There are two sorts of API to use a Stateless Future, the for-comprehensions style API and "A-Normal Form" style API. 148 | 149 | ### For-Comprehensions 150 | 151 | The for-comprehensions style API for `stateless-future` is like the [for-comprehensions for scala.concurrent.Future](http://docs.scala-lang.org/overviews/core/futures.html#functional_composition_and_forcomprehensions). 152 | 153 | for (total <- sleep10seconds) { 154 | println("After evaluation of the Stateless Future `sleep10seconds`") 155 | println(s"I slept $total times in total.") 156 | executor.shutdown() 157 | } 158 | 159 | A notable difference between the two for-comprehensions implementations is the required implicit parameter. A `scala.concurrent.Future` requires an `ExecutionContext`, while a Stateless Future requires a `Catcher`. 160 | 161 | import scala.util.control.Exception.Catcher 162 | implicit def catcher: Catcher[Unit] = { 163 | case e: Exception => { 164 | println("An exception occured when I was sleeping: " + e.getMessage) 165 | } 166 | } 167 | 168 | ### A-Normal Form 169 | 170 | "A-Normal Form" style API for Stateless Futures is like the pending proposal [scala.async](http://docs.scala-lang.org/sips/pending/async.html), except Stateless Futures require less limitations than `scala.async`. 171 | 172 | val sleep10seconds = Future { 173 | var i = 0 174 | while (i < 10) { 175 | println(s"I have slept $i times") 176 | // The magic postfix `await` invokes asynchronous method like normal `Thread.sleep()`, 177 | // and does not block any thread. 178 | asyncSleep(1.seconds).await 179 | i += 1 180 | } 181 | i 182 | } 183 | 184 | The `Future` function for Stateless Futures corresponds to `async` method in `Async`, and the `await` postfix to Stateless Futures corresponds to `await` method in `Async`. 185 | 186 | ## Design 187 | 188 | Regardless of the familiar veneers between Stateless Futures and `scala.concurrent.Future`, I have made some different designed choices on Stateless Futures. 189 | 190 | ### Statelessness 191 | 192 | The Stateless Futures are pure functional, thus they will never store result values or exceptions. Instead, Stateless Futures evaluate lazily, and they do the same job every time you invoke `foreach` or `onComplete`. The behavior of Stateless Futures is more like monads in Haskell than futures in Java. 193 | 194 | Also, there is no `isComplete` method in Stateless Futures. As a result, the users of Stateless Futures are forced not to share futures between threads, not to check the states in futures. They have to care about control flows instead of threads, and build the control flows by defining Stateless Futures. 195 | 196 | ### Threading-free Model 197 | 198 | There are too many threading models and implimentations in the Java/Scala world, `java.util.concurrent.Executor`, `scala.concurrent.ExecutionContext`, `javax.swing.SwingUtilities.invokeLater`, `java.util.Timer`, ... It is very hard to communicate between threading models. When a developer is working with multiple threading models, he must very carefully pass messages between threading models, or he have to maintain bulks of `synchronized` methods to properly deal with the shared variables between threads. 199 | 200 | Why does he need multiple threading models? Because the libraries that he uses depend on different threading modes. For example, you must update Swing components in the Swing's UI thread, you must specify `java.util.concurrent.ExecutionService`s for `java.nio.channels.CompletionHandler`, and, you must specify `scala.concurrent.ExecutionContext`s for `scala.concurrent.Future` and `scala.async.Async`. Oops! 201 | 202 | Think about somebody who uses Swing to develop a text editor software. He wants to create a state machine to update UI. He have heard the cool `scala.async`, then he uses the cool "A-Normal Form" expression in `async` to build the state machine that updates UI, and he types `import scala.concurrent.ExecutionContext.Implicits._` to suppress the compiler errors. Everything looks pretty, except the software always crashes. 203 | 204 | Fortunately, `stateless-future` depends on none of these threading model, and cooperates with all of these threading models. If the poor guy tries Stateless Future, replacing `async { }` to `stateless-future`'s `Future { }`, deleting the `import scala.concurrent.ExecutionContext.Implicits._`, he will find that everything looks pretty like before, and does not crash any more. That's why threading-free model is important. 205 | 206 | ### Exception Handling 207 | 208 | There were two `Future` implementations in Scala standard library, `scala.actors.Future` and `scala.concurrent.Future`. `scala.actors.Future`s are not designed to handling exceptions, since exceptions are always handled by actors. There is no way to handle a particular exception in a particular subrange of an actor. 209 | 210 | Unlike `scala.actors.Future`s, `scala.concurrent.Future`s are designed to handle exceptions. But, unfortunately, `scala.concurrent.Future`s provide too many mechanisms to handle an exception. For example: 211 | 212 | import scala.concurrent.Await 213 | import scala.concurrent.ExecutionContext 214 | import scala.concurrent.duration.Duration 215 | import scala.util.control.Exception.Catcher 216 | import scala.concurrent.forkjoin.ForkJoinPool 217 | val threadPool = new ForkJoinPool() 218 | val catcher1: Catcher[Unit] = { case e: Exception => println("catcher1") } 219 | val catcher2: Catcher[Unit] = { 220 | case e: java.io.IOException => println("catcher2") 221 | case other: Exception => throw new RuntimeException(other) 222 | } 223 | val catcher3: Catcher[Unit] = { 224 | case e: java.io.IOException => println("catcher3") 225 | case other: Exception => throw new RuntimeException(other) 226 | } 227 | val catcher4: Catcher[Unit] = { case e: Exception => println("catcher4") } 228 | val catcher5: Catcher[Unit] = { case e: Exception => println("catcher5") } 229 | val catcher6: Catcher[Unit] = { case e: Exception => println("catcher6") } 230 | val catcher7: Catcher[Unit] = { case e: Exception => println("catcher7") } 231 | def future1 = scala.concurrent.future { 1 }(ExecutionContext.fromExecutor(threadPool, catcher1)) 232 | def future2 = scala.concurrent.Future.failed(new Exception) 233 | val composedFuture = future1.flatMap { _ => future2 }(ExecutionContext.fromExecutor(threadPool, catcher2)) 234 | composedFuture.onFailure(catcher3)(ExecutionContext.fromExecutor(threadPool, catcher4)) 235 | composedFuture.onFailure(catcher5)(ExecutionContext.fromExecutor(threadPool, catcher6)) 236 | try { Await.result(composedFuture, Duration.Inf) } catch { case e if catcher7.isDefinedAt(e) => catcher7(e) } 237 | 238 | Is any sane developer able to tell which catchers will receive the exceptions? 239 | 240 | There are too many concepts about exceptions when you work with `scala.concurrent.Future`. You have to remember the different exception handling strategies between `flatMap`, `recover`, `recoverWith` and `onFailure`, and the difference between `scala.concurrent.Future.failed(new Exception)` and `scala.concurrent.future { throw new Exception }`. 241 | 242 | `scala.async` does not make things better, because `scala.async` will [produce a compiler error](https://github.com/scala/async/blob/master/src/test/scala/scala/async/neg/NakedAwait.scala#L104) for every `await` in a `try` statement. 243 | 244 | Fortunately, you can get rid of all those concepts if you switch to `stateless-future`. There is neither `catcher` implicit parameter in `flatMap` or `map` in Stateless Futures, nor `onFailure` nor `recover` method at all. You just simply `try`, and things get done. See [the examples](https://github.com/Atry/stateless-future-test/blob/2.10.x/test/src/test/scala/com/qifun/statelessFuture/test/run/exceptions/ExceptionsSpec.scala#L62) to learn that. 245 | 246 | ### Tail Call Optimization 247 | 248 | Tail call optimization is an important feature for pure functional programming. Without tail call optimization, many recursive algorithm will fail at run-time, and you will get the well-known `StackOverflowError`. 249 | 250 | The Scala language provides `scala.annotation.tailrec` to automatically optimize simple tail recursions, and `scala.util.control.TailCalls` to manually optimize complex tail calls. 251 | 252 | `stateless-future` project is internally based on `scala.util.control.TailCalls`, and automatically performs tail call optimization in the magic `Future` blocks, without any additional special syntax. 253 | 254 | See [this example](https://github.com/Atry/stateless-future-test/blob/2.10.x/test/src/test/scala/com/qifun/statelessFuture/test/run/tailcall/TailcallSpec.scala). The example creates 500,000,000 stack levels recursively. And it just works, without any `StackOverflowError` or `OutOfMemoryError`. Note that if you port this example for `scala.async` it will throw an `OutOfMemoryError` or a `TimeoutException`. 255 | 256 | ## Installation 257 | 258 | Put these lines in your `build.sbt` if you use [Sbt](http://www.scala-sbt.org/): 259 | 260 | libraryDependencies += "com.qifun" %% "stateless-future" % "0.3.2" 261 | 262 | `stateless-future` should work with Scala 2.10.3, 2.10.4, or 2.11.x. 263 | 264 | ## Known issues 265 | 266 | * `lazy val`s in magic `Future` block are not supported, due to [scala issue 8499](https://issues.scala-lang.org/browse/SI-8499). 267 | * `unapplySeq`s in magic `Future` block are not supported, due to [scala issue 8825](https://issues.scala-lang.org/browse/SI-8825). 268 | * In [some rare cases](https://github.com/Atry/stateless-future-test/blob/2.10.x/test/src/test/scala/com/qifun/statelessFuture/test/run/match0/Match0.scala#L85), if you create multiple `val` with same name in one `Future` block, the last `val` may be referred unexpectly. 269 | * [Some complex existential types](https://github.com/Atry/stateless-future-test/blob/2.10.x/test/src/test/scala/com/qifun/statelessFuture/test/run/uncheckedBounds/UncheckedBoundsSpec.scala) may cause compiler errors, , due to [scala issue 8500](https://issues.scala-lang.org/browse/SI-8500). 270 | 271 | Clone [stateless-future-test](https://github.com/Atry/stateless-future-test) and run the test cases to check these limitations. 272 | 273 | ## Community 274 | 275 | Discussion around Stateless Future is currently happening in the 276 | [Gitter channel](https://gitter.im/Atry/stateless-future) as well as on Github 277 | issue and PR pages. 278 | 279 | People are expected to follow the 280 | [Typelevel Code of Conduct](http://typelevel.org/conduct.html) when 281 | discussing Cats on the Github page, Gitter channel, or other 282 | venues. 283 | 284 | ## Links 285 | * [The API documentation](https://oss.sonatype.org/service/local/repositories/releases/archive/com/qifun/stateless-future_2.11/0.4.0/stateless-future_2.11-0.4.0-javadoc.jar/!/index.html) 286 | * [Utilities](https://github.com/Atry/stateless-future-util) 287 | * [Akka integration](https://github.com/Atry/stateless-future-akka) 288 | * [Tests and examples](https://github.com/Atry/stateless-future-test/tree/2.10.x/test/src/test/scala/com/qifun/statelessFuture/test) 289 | 290 |
291 | 292 | 293 | 294 | 306 | 318 | 319 |
295 |
明日歌
296 |

297 | 明日复明日,
298 | 明日何其多。
299 | 我生待明日,
300 | 万事成蹉跎。
301 |

302 |

303 | 文嘉/錢鶴灘 304 |

305 |
307 |
Future Song
308 |

309 | The Future flatMaps a Future.
310 | The Future tailcalls forever.
311 | My life to await the Future.
312 | It comes OutOfMemoryError. 313 |

314 |

315 | Wen Jia / Qian Hetan 316 |

317 |
320 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | organization := "com.qifun" 2 | 3 | name := "stateless-future" 4 | 5 | libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value 6 | 7 | libraryDependencies += "junit" % "junit-dep" % "4.10" % "test" 8 | 9 | libraryDependencies += "com.novocode" % "junit-interface" % "0.10" % "test" 10 | 11 | scalacOptions ++= Seq("-optimize", "-unchecked", "-Xlint", "-feature") 12 | 13 | scalacOptions ++= { 14 | if (scalaVersion.value.startsWith("2.10.")) { 15 | Seq("-deprecation") // Fully compatible with 2.10.x 16 | } else { 17 | Seq() // May use deprecated API in 2.11.x 18 | } 19 | } 20 | 21 | crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.1") 22 | 23 | description := "The rubost asynchronous programming facility for Scala that offers a direct API for working with Futures." 24 | 25 | startYear := Some(2014) 26 | -------------------------------------------------------------------------------- /deploy.sbt.disabled: -------------------------------------------------------------------------------- 1 | enablePlugins(Travis) 2 | 3 | enablePlugins(SonatypeRelease) 4 | 5 | lazy val secret = project settings(publishArtifact := false) configure { secret => 6 | sys.env.get("SECRET_GIST") match { 7 | case Some(gist) => 8 | secret.addSbtFilesFromGit(gist, file("secret.sbt")) 9 | case None => 10 | secret 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.13 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.thoughtworks.sbt-best-practice" % "sbt-best-practice" % "latest.release") 2 | -------------------------------------------------------------------------------- /src/main/scala/com/qifun/statelessFuture/ANormalForm.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * stateless-future 3 | * Copyright 2014 深圳岂凡网络有限公司 (Shenzhen QiFun Network Corp., LTD) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.qifun.statelessFuture 19 | 20 | import scala.runtime.AbstractPartialFunction 21 | import scala.reflect.macros.Context 22 | import scala.util.control.Exception.Catcher 23 | import scala.util.control.TailCalls._ 24 | import scala.concurrent.ExecutionContext 25 | import scala.util.Success 26 | import scala.util.Failure 27 | 28 | /** 29 | * Used internally only. 30 | */ 31 | object ANormalForm { 32 | 33 | abstract class AbstractAwaitable[+AwaitResult, TailRecResult] extends Awaitable.Stateless[AwaitResult, TailRecResult] 34 | 35 | @inline 36 | final def forceOnComplete[TailRecResult](future: Awaitable[Any, TailRecResult], handler: Nothing => TailRec[TailRecResult])(implicit catcher: Catcher[TailRec[TailRecResult]]): TailRec[TailRecResult] = { 37 | future.onComplete(handler.asInstanceOf[Any => TailRec[TailRecResult]]) 38 | } 39 | 40 | final class TryCatchFinally[AwaitResult, TailRecResult](tryFuture: Awaitable[AwaitResult, TailRecResult], getCatcherFuture: Catcher[Awaitable[AwaitResult, TailRecResult]], finallyBlock: => Unit) extends AbstractAwaitable[AwaitResult, TailRecResult] { 41 | @inline 42 | override final def onComplete(rest: AwaitResult => TailRec[TailRecResult])(implicit catcher: Catcher[TailRec[TailRecResult]]) = { 43 | tryFuture.onComplete { a => 44 | // 成功执行 try 45 | try { 46 | finallyBlock 47 | tailcall(rest(a)) 48 | } catch { 49 | case e if catcher.isDefinedAt(e) => { 50 | // 执行finally时出错 51 | tailcall(catcher(e)) 52 | } 53 | } 54 | } { 55 | case e if getCatcherFuture.isDefinedAt(e) => { 56 | // 执行 try 失败,用getCatcherFuture进行恢复 57 | val catcherFuture = getCatcherFuture(e) 58 | catcherFuture.onComplete { a => 59 | // 成功恢复 60 | try { 61 | finallyBlock 62 | tailcall(rest(a)) 63 | } catch { 64 | case e if catcher.isDefinedAt(e) => { 65 | // 执行finally时出错 66 | tailcall(catcher(e)) 67 | } 68 | } 69 | } { 70 | case e if catcher.isDefinedAt(e) => { 71 | // 执行 try 失败,getCatcherFuture恢复失败,触发外层异常 72 | try { 73 | finallyBlock 74 | tailcall(catcher(e)) 75 | } catch { 76 | case e if catcher.isDefinedAt(e) => { 77 | // 执行finally时出错 78 | tailcall(catcher(e)) 79 | } 80 | } 81 | } 82 | case e: Throwable => { 83 | finallyBlock 84 | throw e 85 | } 86 | } 87 | } 88 | case e if catcher.isDefinedAt(e) => { 89 | // 执行 try 失败,getCatcherFuture不支持恢复,触发外层异常 90 | try { 91 | finallyBlock 92 | tailcall(catcher(e)) 93 | } catch { 94 | case e if catcher.isDefinedAt(e) => { 95 | // 执行finally时出错 96 | tailcall(catcher(e)) 97 | } 98 | } 99 | } 100 | case e: Throwable => { 101 | // 执行 try 失败,getCatcherFuture不支持恢复,外层异常 102 | finallyBlock 103 | throw e 104 | } 105 | } 106 | } 107 | } 108 | 109 | def applyMacro(c: Context)(block: c.Expr[Any]): c.Expr[Nothing] = { 110 | import c.universe._ 111 | c.macroApplication match { 112 | case Apply(TypeApply(_, List(t)), _) => { 113 | applyMacroWithType(c)(block, t, Ident(typeOf[Unit].typeSymbol)) 114 | } 115 | case Apply(TypeApply(_, List(t, tailRecResultTypeTree)), _) => { 116 | applyMacroWithType(c)(block, t, tailRecResultTypeTree) 117 | } 118 | } 119 | } 120 | 121 | def applyMacroWithType(c: Context)(futureBody: c.Expr[Any], macroAwaitResultTypeTree: c.Tree, tailRecResultTypeTree: c.Tree): c.Expr[Nothing] = { 122 | 123 | import c.universe.Flag._ 124 | import c.universe._ 125 | import compat._ 126 | 127 | def unchecked(tree: Tree) = { 128 | Annotated(Apply(Select(New(TypeTree(typeOf[unchecked])), nme.CONSTRUCTOR), List()), tree) 129 | } 130 | 131 | val abstractPartialFunction = typeOf[AbstractPartialFunction[_, _]] 132 | val futureType = typeOf[Awaitable[_, _]] 133 | val statelessFutureType = typeOf[Awaitable.Stateless[_, _]] 134 | val futureClassType = typeOf[AbstractAwaitable[_, _]] 135 | val function1Type = typeOf[_ => _] 136 | val function1Symbol = function1Type.typeSymbol 137 | 138 | val tailRecType = typeOf[TailRec[_]] 139 | val tailRecSymbol = tailRecType.typeSymbol 140 | val uncheckedSymbol = typeOf[scala.unchecked].typeSymbol 141 | val AndSymbol = typeOf[Boolean].declaration(newTermName("&&").encodedName) 142 | val OrSymbol = typeOf[Boolean].declaration(newTermName("||").encodedName) 143 | 144 | val tailRecTypeTree = AppliedTypeTree(Ident(tailRecSymbol), List(tailRecResultTypeTree)) 145 | val catcherTypeTree = AppliedTypeTree(Ident(typeOf[PartialFunction[_, _]].typeSymbol), List(Ident(typeOf[Throwable].typeSymbol), tailRecTypeTree)) 146 | val resultTypeTree = AppliedTypeTree(Ident(statelessFutureType.typeSymbol), List(macroAwaitResultTypeTree, tailRecResultTypeTree)) 147 | def checkNakedAwait(tree: Tree, errorMessage: String) { 148 | for (subTree @ Select(future, await) <- tree if await.decoded == "await" && future.tpe <:< futureType) { 149 | c.error(subTree.pos, errorMessage) 150 | } 151 | } 152 | 153 | def transformParameterList(isByNameParam: List[Boolean], trees: List[Tree], catcher: Tree, rest: (List[Tree]) => Tree)(implicit forceAwait: Set[Name]): Tree = { 154 | trees match { 155 | case Nil => rest(Nil) 156 | case head :: tail => { 157 | head match { 158 | case Typed(origin, typeTree) => 159 | transform(origin, catcher, new NotTailcall { 160 | override final def apply(transformedOrigin: Tree) = { 161 | val parameterName = newTermName(c.fresh("yangBoParameter")) 162 | Block( 163 | List(ValDef(Modifiers(), parameterName, TypeTree(), transformedOrigin).setPos(origin.pos)), 164 | transformParameterList(if (isByNameParam.nonEmpty) isByNameParam.tail else Nil, tail, catcher, { transformedTail => 165 | rest(treeCopy.Typed(head, Ident(parameterName), typeTree) :: transformedTail) 166 | })) 167 | } 168 | }) 169 | case _ if isByNameParam.nonEmpty && isByNameParam.head => { 170 | checkNakedAwait(head, "await must not be used under a by-name argument.") 171 | transformParameterList(isByNameParam.tail, tail, catcher, { (transformedTail) => 172 | rest(head :: transformedTail) 173 | }) 174 | } 175 | case _ => 176 | transform(head, catcher, new NotTailcall { 177 | override final def apply(transformedHead: Tree) = { 178 | val parameterName = newTermName(c.fresh("yangBoParameter")) 179 | Block( 180 | List(ValDef(Modifiers(), parameterName, TypeTree(), transformedHead).setPos(head.pos)), 181 | transformParameterList(if (isByNameParam.nonEmpty) isByNameParam.tail else Nil, tail, catcher, { transformedTail => 182 | rest(Ident(parameterName) :: transformedTail) 183 | })) 184 | } 185 | }) 186 | 187 | } 188 | } 189 | } 190 | } 191 | 192 | def newCatcher(cases: List[CaseDef], typeTree: Tree): Tree = { 193 | val catcherClassName = newTypeName(c.fresh("YangBoCatcher")) 194 | val isDefinedCases = ((for (originCaseDef @ CaseDef(pat, guard, _) <- cases.view) yield { 195 | treeCopy.CaseDef(originCaseDef, pat, guard, Literal(Constant(true))) 196 | }) :+ CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false)))).toList 197 | val defaultName = newTermName(c.fresh("default")) 198 | val throwableName = newTermName(c.fresh("throwable")) 199 | val applyOrElseCases = cases :+ CaseDef(Ident(nme.WILDCARD), EmptyTree, Apply(Ident(defaultName), List(Ident(throwableName)))) 200 | Block( 201 | List( 202 | ClassDef( 203 | Modifiers(FINAL), 204 | catcherClassName, 205 | List(), 206 | Template( 207 | List( 208 | AppliedTypeTree( 209 | Ident(abstractPartialFunction.typeSymbol), 210 | List( 211 | TypeTree(typeOf[Throwable]), 212 | typeTree))), 213 | emptyValDef, 214 | List( 215 | DefDef( 216 | Modifiers(), 217 | nme.CONSTRUCTOR, 218 | List(), 219 | List(List()), 220 | TypeTree(), 221 | Block( 222 | List( 223 | Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), 224 | Literal(Constant(())))), 225 | DefDef( 226 | Modifiers(OVERRIDE | FINAL), 227 | newTermName("applyOrElse"), 228 | List( 229 | TypeDef(Modifiers(PARAM), newTypeName("A1"), List(), TypeBoundsTree(TypeTree(typeOf[Nothing]), TypeTree(typeOf[Throwable]))), 230 | TypeDef(Modifiers(PARAM), newTypeName("B1"), List(), TypeBoundsTree(typeTree, TypeTree(typeOf[Any])))), 231 | List(List( 232 | ValDef( 233 | Modifiers(PARAM), 234 | throwableName, 235 | Ident(newTypeName("A1")), 236 | EmptyTree), 237 | ValDef( 238 | Modifiers(PARAM), 239 | defaultName, 240 | AppliedTypeTree(Ident(function1Symbol), List(Ident(newTypeName("A1")), Ident(newTypeName("B1")))), 241 | EmptyTree))), 242 | Ident(newTypeName("B1")), 243 | Match( 244 | Annotated( 245 | Apply(Select(New(Ident(uncheckedSymbol)), nme.CONSTRUCTOR), List()), 246 | Ident(throwableName)), 247 | applyOrElseCases)), 248 | 249 | DefDef( 250 | Modifiers(OVERRIDE | FINAL), 251 | newTermName("isDefinedAt"), 252 | List(), 253 | List(List(ValDef(Modifiers(PARAM), throwableName, TypeTree(typeOf[Throwable]), EmptyTree))), 254 | TypeTree(typeOf[Boolean]), 255 | Match( 256 | Annotated( 257 | Apply(Select(New(Ident(uncheckedSymbol)), nme.CONSTRUCTOR), List()), 258 | Ident(throwableName)), 259 | isDefinedCases)))))), 260 | Apply(Select(New(Ident(catcherClassName)), nme.CONSTRUCTOR), List())) 261 | 262 | } 263 | 264 | sealed trait Rest { 265 | def apply(former: Tree): Tree 266 | def transformAwait(future: Tree, awaitTypeTree: TypTree, catcher: Tree)(implicit forceAwait: Set[Name]): Tree 267 | } 268 | 269 | // ClassFormatError will be thrown if changing NotTailcall to a trait. scalac's Bug? 270 | abstract class NotTailcall extends Rest { 271 | override final def transformAwait(future: Tree, awaitTypeTree: TypTree, catcher: Tree)(implicit forceAwait: Set[Name]): Tree = { 272 | val nextFutureName = newTermName(c.fresh("yangBoNextFuture")) 273 | val futureExpr = c.Expr(ValDef(Modifiers(), nextFutureName, TypeTree(), future)) 274 | val AwaitResult = newTermName(c.fresh("awaitValue")) 275 | val catcherExpr = c.Expr[Catcher[Nothing]](catcher) 276 | val ANormalFormTree = reify(_root_.com.qifun.statelessFuture.ANormalForm).tree 277 | val ForceOnCompleteName = newTermName("forceOnComplete") 278 | val CatcherTree = catcher 279 | val onCompleteCallExpr = c.Expr( 280 | Apply( 281 | Apply( 282 | TypeApply( 283 | Select(ANormalFormTree, ForceOnCompleteName), 284 | List(tailRecResultTypeTree)), 285 | List( 286 | Ident(nextFutureName), 287 | { 288 | val restTree = NotTailcall.this(Ident(AwaitResult)) 289 | val tailcallSymbol = reify(scala.util.control.TailCalls).tree.symbol 290 | val TailcallName = newTermName("tailcall") 291 | val ApplyName = newTermName("apply") 292 | val AsInstanceOfName = newTermName("asInstanceOf") 293 | def function(awaitValDef: ValDef, restTree: Tree) = { 294 | val functionName = newTermName(c.fresh("yangBoHandler")) 295 | val restExpr = c.Expr(restTree) 296 | Block( 297 | List( 298 | DefDef( 299 | Modifiers(NoFlags, nme.EMPTY, List(Apply(Select(New(Ident(typeOf[scala.inline].typeSymbol)), nme.CONSTRUCTOR), List()))), 300 | functionName, List(), List(List(awaitValDef)), TypeTree(), reify { 301 | try { 302 | restExpr.splice 303 | } catch { 304 | case e if catcherExpr.splice.isDefinedAt(e) => { 305 | _root_.scala.util.control.TailCalls.tailcall(catcherExpr.splice(e)) 306 | } 307 | } 308 | }.tree)), 309 | Ident(functionName)) 310 | } 311 | restTree match { 312 | case Block(List(oldVal @ ValDef(_, capturedResultName, _, Ident(AwaitResult))), expr) => { 313 | // 参数名优化 314 | function(treeCopy.ValDef(oldVal, Modifiers(PARAM), capturedResultName, awaitTypeTree, EmptyTree), expr) 315 | } 316 | case Block(List(oldVal @ ValDef(_, capturedResultName, _, Ident(AwaitResult)), restStates @ _*), expr) => { 317 | // 参数名优化 318 | function(treeCopy.ValDef(oldVal, Modifiers(PARAM), capturedResultName, awaitTypeTree, EmptyTree), Block(restStates.toList, expr)) 319 | } 320 | case _ => { 321 | function(ValDef(Modifiers(PARAM), AwaitResult, awaitTypeTree, EmptyTree), restTree) 322 | } 323 | } 324 | })), 325 | List(catcher))) 326 | reify { 327 | futureExpr.splice 328 | @inline def yangBoTail = onCompleteCallExpr.splice 329 | _root_.scala.util.control.TailCalls.tailcall { yangBoTail } 330 | }.tree 331 | } 332 | 333 | } 334 | 335 | def transform(tree: Tree, catcher: Tree, rest: Rest)(implicit forceAwait: Set[Name]): Tree = { 336 | tree match { 337 | case Try(block, catches, finalizer) => { 338 | val futureName = newTermName(c.fresh("tryCatchFinallyFuture")) 339 | Block( 340 | List( 341 | ValDef(Modifiers(), futureName, TypeTree(), 342 | Apply( 343 | Select( 344 | New( 345 | AppliedTypeTree( 346 | Select(reify(_root_.com.qifun.statelessFuture.ANormalForm).tree, 347 | newTypeName("TryCatchFinally")), 348 | List(TypeTree(tree.tpe), tailRecResultTypeTree))), 349 | nme.CONSTRUCTOR), 350 | List( 351 | newFuture(block).tree, 352 | newCatcher( 353 | for (cd @ CaseDef(pat, guard, body) <- catches) yield { 354 | treeCopy.CaseDef(cd, pat, guard, newFuture(body).tree) 355 | }, 356 | AppliedTypeTree( 357 | Ident(futureType.typeSymbol), 358 | List(TypeTree(tree.tpe), tailRecResultTypeTree))), 359 | if (finalizer.isEmpty) { 360 | Literal(Constant(())) 361 | } else { 362 | checkNakedAwait(finalizer, "await must not be used under a finally.") 363 | finalizer 364 | })))), 365 | rest.transformAwait(Ident(futureName), TypeTree(tree.tpe), catcher)) 366 | 367 | } 368 | case ClassDef(mods, _, _, _) => { 369 | if (mods.hasFlag(TRAIT)) { 370 | checkNakedAwait(tree, "await must not be used under a nested trait.") 371 | } else { 372 | checkNakedAwait(tree, "await must not be used under a nested class.") 373 | } 374 | rest(tree) 375 | } 376 | case _: ModuleDef => { 377 | checkNakedAwait(tree, "await must not be used under a nested object.") 378 | rest(tree) 379 | } 380 | case DefDef(mods, _, _, _, _, _) => { 381 | if (mods.hasFlag(LAZY)) { 382 | checkNakedAwait(tree, "await must not be used under a lazy val initializer.") 383 | } else { 384 | checkNakedAwait(tree, "await must not be used under a nested method.") 385 | } 386 | rest(tree) 387 | } 388 | case _: Function => { 389 | checkNakedAwait(tree, "await must not be used under a nested function.") 390 | rest(tree) 391 | } 392 | case EmptyTree | _: New | _: Ident | _: Literal | _: Super | _: This | _: TypTree | _: New | _: TypeDef | _: Import | _: ImportSelector => { 393 | rest(tree) 394 | } 395 | case Select(future, await) if await.decoded == "await" && future.tpe <:< futureType => { 396 | transform(future, catcher, new NotTailcall { 397 | override final def apply(transformedFuture: Tree) = rest.transformAwait(transformedFuture, TypeTree(tree.tpe), catcher) 398 | }) 399 | } 400 | case Select(instance, field) => { 401 | transform(instance, catcher, new NotTailcall { 402 | override final def apply(transformedInstance: Tree) = rest(treeCopy.Select(tree, transformedInstance, field)) 403 | }) 404 | } 405 | case TypeApply(method, parameters) => { 406 | transform(method, catcher, new NotTailcall { 407 | override final def apply(transformedMethod: Tree) = rest(treeCopy.TypeApply(tree, transformedMethod, parameters)) 408 | }) 409 | } 410 | case Apply(method @ Ident(name), parameters) if forceAwait(name) => { 411 | transformParameterList(Nil, parameters, catcher, { (transformedParameters) => 412 | rest.transformAwait(treeCopy.Apply(tree, Ident(name), transformedParameters), TypeTree(tree.tpe).asInstanceOf[TypTree], catcher) 413 | }) 414 | } 415 | case Apply(method, parameters) => { 416 | transform(method, catcher, new NotTailcall { 417 | override final def apply(transformedMethod: Tree) = { 418 | val isByNameParam = method.symbol match { 419 | case AndSymbol | OrSymbol => { 420 | List(true) 421 | } 422 | case _ => { 423 | method.tpe match { 424 | case MethodType(params, _) => { 425 | for (param <- params) yield { 426 | param.asTerm.isByNameParam 427 | } 428 | } 429 | case _ => { 430 | Nil 431 | } 432 | } 433 | } 434 | } 435 | transformParameterList(isByNameParam, parameters, catcher, { (transformedParameters) => 436 | rest(treeCopy.Apply( 437 | tree, 438 | transformedMethod, 439 | transformedParameters)) 440 | }) 441 | } 442 | }) 443 | } 444 | case Block(stats, expr) => { 445 | def addHead(head: Tree, tuple: (Tree, Boolean)): Tree = { 446 | val (tail, mergeable) = tuple 447 | tail match { 448 | case Block(stats, expr) if mergeable => { 449 | treeCopy.Block(tree, head :: stats, expr) 450 | } 451 | case _ => { 452 | treeCopy.Block(tree, List(head), tail) 453 | } 454 | } 455 | } 456 | def transformBlock(stats: List[Tree])(implicit forceAwait: Set[Name]): (Tree, Boolean) = { 457 | stats match { 458 | case Nil => { 459 | (transform(expr, catcher, new Rest { 460 | 461 | override final def transformAwait(future: Tree, awaitTypeTree: TypTree, catcher: Tree)(implicit forceAwait: Set[Name]): Tree = { 462 | Block(Nil, rest.transformAwait(future, awaitTypeTree, catcher)) 463 | } 464 | 465 | override final def apply(transformedExpr: Tree) = 466 | Block(Nil, rest(transformedExpr)) 467 | }), false) 468 | } 469 | case head :: tail => { 470 | (transform(head, catcher, new NotTailcall { 471 | override final def apply(transformedHead: Tree) = 472 | transformedHead match { 473 | case _: Ident | _: Literal => { 474 | val (block, _) = transformBlock(tail) 475 | block 476 | } 477 | case _ => { 478 | addHead(transformedHead, transformBlock(tail)) 479 | } 480 | } 481 | }), true) 482 | } 483 | } 484 | } 485 | Block(Nil, transformBlock(stats)._1) 486 | } 487 | case ValDef(mods, name, tpt, rhs) => { 488 | transform(rhs, catcher, new NotTailcall { 489 | override final def apply(transformedRhs: Tree) = 490 | rest(treeCopy.ValDef(tree, mods, name, tpt, transformedRhs)) 491 | }) 492 | } 493 | case Assign(left, right) => { 494 | transform(left, catcher, new NotTailcall { 495 | override final def apply(transformedLeft: Tree) = 496 | transform(right, catcher, new NotTailcall { 497 | override final def apply(transformedRight: Tree) = 498 | rest(treeCopy.Assign(tree, transformedLeft, transformedRight)) 499 | }) 500 | }) 501 | } 502 | case Match(selector, cases) => { 503 | transform(selector, catcher, new NotTailcall { 504 | override final def apply(transformedSelector: Tree) = 505 | rest.transformAwait( 506 | treeCopy.Match( 507 | tree, 508 | transformedSelector, 509 | for (originCaseDef @ CaseDef(pat, guard, body) <- cases) yield { 510 | checkNakedAwait(guard, "await must not be used under a pattern guard.") 511 | treeCopy.CaseDef(originCaseDef, 512 | pat, 513 | guard, 514 | newFutureAsType(body, TypeTree(tree.tpe)).tree) 515 | }), 516 | TypeTree(tree.tpe), 517 | catcher) 518 | }) 519 | } 520 | case If(cond, thenp, elsep) => { 521 | transform(cond, catcher, new NotTailcall { 522 | override final def apply(transformedCond: Tree) = 523 | rest.transformAwait( 524 | If( 525 | transformedCond, 526 | newFuture(thenp).tree, 527 | newFuture(elsep).tree), 528 | TypeTree(tree.tpe), 529 | catcher) 530 | }) 531 | } 532 | case Throw(throwable) => { 533 | transform(throwable, catcher, new NotTailcall { 534 | override final def apply(transformedThrowable: Tree) = 535 | rest(treeCopy.Throw(tree, transformedThrowable)) 536 | }) 537 | } 538 | case Typed(expr, tpt@Ident(tpnme.WILDCARD_STAR)) => { 539 | transform(expr, catcher, new NotTailcall { 540 | override final def apply(transformedExpr: Tree) = 541 | rest(treeCopy.Typed(tree, transformedExpr, tpt)) 542 | }) 543 | } 544 | case Typed(expr, tpt) => { 545 | transform(expr, catcher, new NotTailcall { 546 | override final def apply(transformedExpr: Tree) = 547 | rest(treeCopy.Typed(tree, transformedExpr, TypeTree(tpt.tpe))) 548 | }) 549 | } 550 | case Annotated(annot, arg) => { 551 | transform(arg, catcher, new NotTailcall { 552 | override final def apply(transformedArg: Tree) = 553 | rest(treeCopy.Annotated(tree, annot, transformedArg)) 554 | }) 555 | } 556 | case LabelDef(name, params, rhs) => { 557 | Block( 558 | List( 559 | DefDef(Modifiers(), 560 | name, 561 | List(), 562 | List( 563 | for (p <- params) yield { 564 | ValDef(Modifiers(PARAM), p.name.toTermName, TypeTree(p.tpe), EmptyTree) 565 | }), 566 | AppliedTypeTree(Ident(futureType.typeSymbol), List(TypeTree(tree.tpe), tailRecResultTypeTree)), 567 | newFuture(rhs)(forceAwait + name).tree)), 568 | rest.transformAwait( 569 | Apply( 570 | Ident(name), 571 | params), 572 | TypeTree(tree.tpe), 573 | catcher)) 574 | } 575 | case _: Return => { 576 | c.error(tree.pos, "return is illegal.") 577 | rest(tree) 578 | } 579 | case _: PackageDef | _: Template | _: CaseDef | _: Alternative | _: Star | _: Bind | _: UnApply | _: AssignOrNamedArg | _: ReferenceToBoxed => { 580 | c.error(tree.pos, s"Unexpected expression in a `Future` block") 581 | rest(tree) 582 | } 583 | } 584 | } 585 | 586 | def newFutureAsType(tree: Tree, awaitValueTypeTree: Tree)(implicit forceAwait: Set[Name]): c.Expr[Awaitable.Stateless[Nothing, _]] = { 587 | 588 | val statelessFutureTypeTree = AppliedTypeTree(Ident(statelessFutureType.typeSymbol), List(awaitValueTypeTree, tailRecResultTypeTree)) 589 | val futureClassTypeTree = AppliedTypeTree(Ident(futureClassType.typeSymbol), List(awaitValueTypeTree, tailRecResultTypeTree)) 590 | val ANormalFormTree = reify(_root_.com.qifun.statelessFuture.ANormalForm).tree 591 | val ForceOnCompleteName = newTermName("forceOnComplete") 592 | 593 | val futureName = newTypeName(c.fresh("YangBoFuture")) 594 | val returnName = newTermName(c.fresh("yangBoReturn")) 595 | 596 | val catcherName = newTermName(c.fresh("yangBoCatcher")) 597 | c.Expr( 598 | Block( 599 | List( 600 | ClassDef( 601 | Modifiers(FINAL), 602 | futureName, 603 | List(), 604 | Template( 605 | List(futureClassTypeTree), 606 | emptyValDef, 607 | List( 608 | DefDef( 609 | Modifiers(), 610 | nme.CONSTRUCTOR, 611 | List(), 612 | List(List()), 613 | TypeTree(), 614 | Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), 615 | DefDef(Modifiers(OVERRIDE | FINAL), 616 | newTermName("onComplete"), 617 | List(), 618 | List( 619 | List(ValDef( 620 | Modifiers(PARAM), 621 | returnName, 622 | AppliedTypeTree(Ident(function1Symbol), List(awaitValueTypeTree, tailRecTypeTree)), 623 | EmptyTree)), 624 | List(ValDef( 625 | Modifiers(IMPLICIT | PARAM), 626 | catcherName, 627 | catcherTypeTree, 628 | EmptyTree))), 629 | tailRecTypeTree, 630 | { 631 | val catcherExpr = c.Expr[Catcher[TailRec[Nothing]]](Ident(catcherName)) 632 | val tryBodyExpr = c.Expr(transform( 633 | tree, 634 | Ident(catcherName), 635 | new Rest { 636 | 637 | override final def transformAwait(future: Tree, awaitTypeTree: TypTree, catcher: Tree)(implicit forceAwait: Set[Name]): Tree = { 638 | 639 | val nextFutureName = newTermName(c.fresh("yangBoNextFuture")) 640 | val futureExpr = c.Expr(ValDef(Modifiers(), nextFutureName, TypeTree(), future)) 641 | val onCompleteCallExpr = c.Expr( 642 | Apply( 643 | Apply( 644 | TypeApply( 645 | Select(ANormalFormTree, ForceOnCompleteName), 646 | List(tailRecResultTypeTree)), 647 | List( 648 | Ident(nextFutureName), 649 | Ident(returnName))), 650 | List(catcher))) 651 | 652 | reify { 653 | futureExpr.splice 654 | @inline def yangBoTail = onCompleteCallExpr.splice 655 | _root_.scala.util.control.TailCalls.tailcall { yangBoTail } 656 | }.tree 657 | 658 | } 659 | 660 | override final def apply(x: Tree) = { 661 | val resultExpr = c.Expr(x) 662 | val returnExpr = c.Expr[Any => Nothing](Ident(returnName)) 663 | reify { 664 | val result = resultExpr.splice 665 | // Workaround for some nested Future blocks. 666 | _root_.scala.util.control.TailCalls.tailcall((returnExpr.splice).asInstanceOf[Any => _root_.scala.util.control.TailCalls.TailRec[Nothing]].apply(result)) 667 | }.tree 668 | } 669 | })) 670 | reify { 671 | try { 672 | tryBodyExpr.splice 673 | } catch { 674 | case e if catcherExpr.splice.isDefinedAt(e) => { 675 | _root_.scala.util.control.TailCalls.tailcall(catcherExpr.splice(e)) 676 | } 677 | } 678 | }.tree 679 | }))))), 680 | Typed( 681 | Apply(Select(New(Ident(futureName)), nme.CONSTRUCTOR), List()), 682 | unchecked(statelessFutureTypeTree)))) 683 | } 684 | def newFuture(tree: Tree)(implicit forceAwait: Set[Name]): c.Expr[Awaitable.Stateless[Nothing, _]] = { 685 | newFutureAsType(tree, TypeTree(tree.tpe.widen)) 686 | } 687 | val result = newFutureAsType(futureBody.tree, macroAwaitResultTypeTree)(Set.empty) 688 | // c.warning(c.enclosingPosition, show(result)) 689 | c.Expr( 690 | TypeApply( 691 | Select( 692 | c.resetLocalAttrs(result.tree), 693 | newTermName("asInstanceOf")), 694 | List(resultTypeTree))) 695 | } 696 | 697 | } 698 | -------------------------------------------------------------------------------- /src/main/scala/com/qifun/statelessFuture/Awaitable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * stateless-future 3 | * Copyright 2014 深圳岂凡网络有限公司 (Shenzhen QiFun Network Corp., LTD) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.qifun.statelessFuture 19 | 20 | import scala.reflect.internal.annotations.compileTimeOnly 21 | import scala.util.control.TailCalls._ 22 | import scala.util.control.Exception.Catcher 23 | import scala.util.Try 24 | import scala.concurrent.ExecutionContext 25 | import scala.util.Success 26 | import scala.util.Failure 27 | 28 | /** 29 | * Something that will be completed in the future. 30 | * @tparam AwaitResult The type that [[await]] returns. 31 | * @tparam TailRecResult The response type, should be `Unit` in most cases. 32 | * @see [[Future]] 33 | */ 34 | sealed trait Awaitable[+AwaitResult, TailRecResult] extends Any { outer => 35 | 36 | /** 37 | * Suspends this [[Awaitable]] until the asynchronous operation being completed, and then returns the result of the asynchronous operation. 38 | * @note The code after [[await]] and the code before [[await]] may be evaluated in different threads. 39 | * @note This method must be in a [[Future.apply]] block or [[Awaitable.apply]] block. 40 | */ 41 | @compileTimeOnly("`await` must be enclosed in a `Future` block") 42 | final def await: AwaitResult = ??? 43 | 44 | /** 45 | * Like [[foreach]], except this method supports tail-call optimization. 46 | */ 47 | def onComplete(handler: AwaitResult => TailRec[TailRecResult])(implicit catcher: Catcher[TailRec[TailRecResult]]): TailRec[TailRecResult] 48 | 49 | /** 50 | * Asks this [[Awaitable]] to pass result to `handler` when the asynchronous operation being completed, 51 | * or to pass the exception to `catcher` when the asynchronous operation being failed, 52 | * and starts the asynchronous operation if this [[Awaitable]] is an [[Awaitable.Stateless]]. 53 | */ 54 | final def foreach(handler: AwaitResult => TailRecResult)(implicit catcher: Catcher[TailRecResult]): TailRecResult = { 55 | onComplete { a => 56 | done(handler(a)) 57 | } { 58 | case e if catcher.isDefinedAt(e) => 59 | done(catcher(e)) 60 | }.result 61 | } 62 | 63 | /** 64 | * Returns a new [[Awaitable.Stateless]] composed of this [[Awaitable]] and the `converter`. 65 | * The new [[Awaitable.Stateless]] will pass the original result to `convert` when the original asynchronous operation being completed, 66 | * or pass the exception to `catcher` when the original asynchronous operation being failed. 67 | */ 68 | final def map[ConvertedAwaitResult](converter: AwaitResult => ConvertedAwaitResult) = new Awaitable.Stateless[ConvertedAwaitResult, TailRecResult] { 69 | def onComplete(k: ConvertedAwaitResult => TailRec[TailRecResult])(implicit catcher: Catcher[TailRec[TailRecResult]]): TailRec[TailRecResult] = { 70 | def apply(a: AwaitResult): TailRec[TailRecResult] = { 71 | val b = try { 72 | converter(a) 73 | } catch { 74 | case e if catcher.isDefinedAt(e) => { 75 | return tailcall(catcher(e)) 76 | } 77 | } 78 | tailcall(k(b)) 79 | } 80 | outer.onComplete(apply) 81 | } 82 | } 83 | 84 | /** 85 | * Returns a new [[Awaitable.Stateless]] composed of this [[Awaitable]] and the `condition`. 86 | * The new [[Awaitable.Stateless]] will pass the original result to `condition` when the original asynchronous operation being completed, 87 | * or pass the exception to `catcher` when the original asynchronous operation being failed. 88 | * 89 | * @throws java.util.NoSuchElementException Passes to `catcher` if the `condition` returns `false`. 90 | */ 91 | final def withFilter(condition: AwaitResult => Boolean) = new Awaitable.Stateless[AwaitResult, TailRecResult] { 92 | def onComplete(k: AwaitResult => TailRec[TailRecResult])(implicit catcher: Catcher[TailRec[TailRecResult]]): TailRec[TailRecResult] = { 93 | def apply(a: AwaitResult): TailRec[TailRecResult] = { 94 | val b = try { 95 | condition(a) 96 | } catch { 97 | case e if catcher.isDefinedAt(e) => { 98 | return tailcall(catcher(e)) 99 | } 100 | } 101 | if (b) { 102 | tailcall(k(a)) 103 | } else { 104 | tailcall(catcher(new NoSuchElementException)) 105 | } 106 | } 107 | outer.onComplete(apply) 108 | } 109 | } 110 | 111 | /** 112 | * Returns a new [[Awaitable.Stateless]] composed of this [[Awaitable]] and the `converter`. 113 | * The new [[Awaitable.Stateless]] will pass the original result to `convert` when the original asynchronous operation being completed, 114 | * or pass the exception to `catcher` when the original asynchronous operation being failed. 115 | */ 116 | final def flatMap[ConvertedAwaitResult](converter: AwaitResult => Awaitable[ConvertedAwaitResult, TailRecResult]) = new Awaitable.Stateless[ConvertedAwaitResult, TailRecResult] { 117 | override final def onComplete(body: ConvertedAwaitResult => TailRec[TailRecResult])(implicit catcher: Catcher[TailRec[TailRecResult]]): TailRec[TailRecResult] = { 118 | def apply(a: AwaitResult): TailRec[TailRecResult] = { 119 | val futureB = try { 120 | converter(a) 121 | } catch { 122 | case e if catcher.isDefinedAt(e) => { 123 | return tailcall(catcher(e)) 124 | } 125 | } 126 | futureB.onComplete { b => 127 | tailcall(body(b)) 128 | } 129 | } 130 | outer.onComplete(apply) 131 | } 132 | } 133 | 134 | } 135 | 136 | object Awaitable { 137 | 138 | /** 139 | * An stateful [[Awaitable]] that represents an asynchronous operation already started. 140 | */ 141 | trait Stateful[+AwaitResult, TailRecResult] extends Any with Awaitable[AwaitResult, TailRecResult] { 142 | 143 | /** 144 | * Tests if this [[Awaitable.Stateful]] is completed. 145 | */ 146 | def isCompleted: Boolean = value.isDefined 147 | 148 | /** 149 | * The result of the asynchronous operation. 150 | */ 151 | def value: Option[Try[AwaitResult]] 152 | 153 | } 154 | 155 | /** 156 | * An stateless [[Awaitable]] that starts a new asynchronous operation whenever [[onComplete]] or [[foreach]] being called. 157 | * @note The result value of the operation will never store in this [[Stateless]]. 158 | */ 159 | trait Stateless[+AwaitResult, TailRecResult] extends Any with Awaitable[AwaitResult, TailRecResult] 160 | 161 | import scala.language.experimental.macros 162 | 163 | /** 164 | * Returns a stateless [[Awaitable]] that evaluates the `block`. 165 | * @param block The asynchronous operation that will perform later. Note that all [[Awaitable.await]] calls must be in the `block`. 166 | */ 167 | def apply[AwaitResult, TailRecResult](block: => AwaitResult): Awaitable.Stateless[AwaitResult, TailRecResult] = macro ANormalForm.applyMacro 168 | 169 | import scala.language.implicitConversions 170 | 171 | implicit def fromResponder[AwaitResult](underlying: Responder[AwaitResult]) = { 172 | new Future.FromResponder(underlying) 173 | } 174 | 175 | implicit def toResponder[AwaitResult](underlying: Future.Stateless[AwaitResult])(implicit catcher: Catcher[Unit]) = { 176 | new Future.ToResponder(underlying) 177 | } 178 | 179 | implicit def fromConcurrentFuture[AwaitResult](underlying: scala.concurrent.Future[AwaitResult])(implicit executor: scala.concurrent.ExecutionContext) = { 180 | new Future.FromConcurrentFuture(underlying) 181 | } 182 | 183 | implicit def toConcurrentFuture[AwaitResult](underlying: Future.Stateful[AwaitResult]) = { 184 | new Future.ToConcurrentFuture(underlying) 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/main/scala/com/qifun/statelessFuture/AwaitableFactory.scala: -------------------------------------------------------------------------------- 1 | package com.qifun.statelessFuture 2 | 3 | import scala.reflect.macros.Context 4 | 5 | object AwaitableFactory { 6 | 7 | /** 8 | * Used internally only. 9 | */ 10 | final def applyMacro(c: Context)(block: c.Expr[Any]): c.Expr[Nothing] = { 11 | import c.universe._ 12 | val Apply(TypeApply(Select(factory, _), List(t)), _) = c.macroApplication 13 | val factoryName = newTermName(c.fresh("yangBoAwaitableFactory")) 14 | val factoryVal = c.Expr(ValDef(Modifiers(), factoryName, TypeTree(), factory)) 15 | val result = ANormalForm.applyMacroWithType(c)(block, t, Select(Ident(factoryName), newTypeName("TailRecResult"))) 16 | reify { 17 | factoryVal.splice 18 | result.splice 19 | } 20 | } 21 | 22 | final def apply[TailRecResult] = new AwaitableFactory[TailRecResult] {} 23 | 24 | } 25 | 26 | trait AwaitableFactory[TRR] { 27 | 28 | type TailRecResult = TRR 29 | 30 | import scala.language.experimental.macros 31 | 32 | /** 33 | * Returns a stateless [[Awaitable]] that evaluates the `block`. 34 | * @param block The asynchronous operation that will perform later. Note that all [[Awaitable.await]] calls must be in the `block`. 35 | */ 36 | final def apply[AwaitResult](block: AwaitResult): Awaitable.Stateless[AwaitResult, TailRecResult] = macro AwaitableFactory.applyMacro 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/com/qifun/statelessFuture/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * stateless-future 3 | * Copyright 2014 深圳岂凡网络有限公司 (Shenzhen QiFun Network Corp., LTD) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.qifun 19 | 20 | import scala.util._ 21 | import scala.util.control.TailCalls._ 22 | import scala.util.control.Exception.Catcher 23 | import scala.concurrent.{ExecutionContext, Promise} 24 | 25 | package object statelessFuture { 26 | 27 | /** 28 | * An [[Awaitable]] that does not need a response type. 29 | */ 30 | type Future[+AwaitResult] = Awaitable[AwaitResult, Unit] 31 | 32 | object Future extends AwaitableFactory[Unit] { 33 | 34 | /** 35 | * An [[Awaitable.Stateless]] that does not need a response type. 36 | */ 37 | type Stateless[+AwaitResult] = Awaitable.Stateless[AwaitResult, Unit] 38 | 39 | /** 40 | * An [[Awaitable.Stateful]] that does not need a response type. 41 | */ 42 | type Stateful[+AwaitResult] = Awaitable.Stateful[AwaitResult, Unit] 43 | 44 | /** 45 | * Forwards all [[Future.Stateful]] API to the underlying `scala.concurrent.Future`. 46 | */ 47 | final class FromConcurrentFuture[AwaitResult](underlying: scala.concurrent.Future[AwaitResult])( 48 | implicit executor: scala.concurrent.ExecutionContext) 49 | extends Future.Stateful[AwaitResult] { 50 | import scala.util._ 51 | 52 | override final def value = underlying.value 53 | 54 | override final def onComplete(body: AwaitResult => TailRec[Unit])( 55 | implicit catcher: Catcher[TailRec[Unit]]): TailRec[Unit] = { 56 | underlying.onComplete { 57 | case Success(successValue) => { 58 | executor.execute(new Runnable { 59 | override final def run(): Unit = { 60 | body(successValue).result 61 | } 62 | }) 63 | } 64 | case Failure(throwable) => { 65 | if (catcher.isDefinedAt(throwable)) { 66 | executor.execute(new Runnable { 67 | override final def run(): Unit = { 68 | catcher(throwable).result 69 | } 70 | }) 71 | } else { 72 | executor.prepare.reportFailure(throwable) 73 | } 74 | } 75 | } 76 | done(()) 77 | } 78 | 79 | } 80 | 81 | /** 82 | * Forwards all `scala.concurrent.Future` API to the underlying [[Future.Stateful]]. 83 | */ 84 | final class ToConcurrentFuture[AwaitResult](underlying: Future.Stateful[AwaitResult]) 85 | extends scala.concurrent.Future[AwaitResult] { 86 | final def transform[S](f: scala.util.Try[AwaitResult] => scala.util.Try[S])( 87 | implicit executor: scala.concurrent.ExecutionContext): scala.concurrent.Future[S] = { 88 | val p = Promise[S] 89 | underlying.onComplete { a => 90 | p.completeWith(concurrent.Future(f(Success(a)).get)) 91 | done(()) 92 | } { 93 | case e: Throwable => 94 | p.completeWith(concurrent.Future(f(Failure(e)).get)) 95 | done(()) 96 | } 97 | p.future 98 | } 99 | 100 | final def transformWith[S](f: scala.util.Try[AwaitResult] => scala.concurrent.Future[S])( 101 | implicit executor: scala.concurrent.ExecutionContext): scala.concurrent.Future[S] = { 102 | val p = Promise[S] 103 | underlying.onComplete { a => 104 | p.completeWith(f(Success(a))) 105 | done(()) 106 | } { 107 | case e: Throwable => 108 | p.completeWith(f(Failure(e))) 109 | done(()) 110 | } 111 | p.future 112 | } 113 | 114 | import scala.concurrent._ 115 | import scala.concurrent.duration.Duration 116 | import scala.util._ 117 | 118 | override final def value: Option[Try[AwaitResult]] = underlying.value 119 | 120 | override final def isCompleted = underlying.isCompleted 121 | 122 | override final def onComplete[U](func: (Try[AwaitResult]) => U)(implicit executor: ExecutionContext) { 123 | underlying.onComplete { a => 124 | executor.prepare.execute(new Runnable { 125 | override final def run() { 126 | ToConcurrentFuture.this.synchronized { 127 | try { 128 | func(Success(a)) 129 | } catch { 130 | case e: Throwable => executor.reportFailure(e) 131 | } 132 | } 133 | } 134 | }) 135 | done(()) 136 | 137 | } { 138 | case e: Throwable => { 139 | executor.prepare.execute(new Runnable { 140 | override final def run() { 141 | ToConcurrentFuture.this.synchronized { 142 | try { 143 | func(Failure(e)) 144 | } catch { 145 | case e: Throwable => executor.reportFailure(e) 146 | } 147 | } 148 | } 149 | }) 150 | done(()) 151 | } 152 | } 153 | 154 | } 155 | 156 | override final def result(atMost: Duration)(implicit permit: CanAwait): AwaitResult = { 157 | ready(atMost) 158 | value.get match { 159 | case Success(successValue) => successValue 160 | case Failure(throwable) => throw throwable 161 | } 162 | } 163 | 164 | override final def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { 165 | if (atMost eq Duration.Undefined) { 166 | throw new IllegalArgumentException 167 | } 168 | synchronized { 169 | if (atMost.isFinite) { 170 | val timeoutAt = atMost.toNanos + System.nanoTime 171 | while (!isCompleted) { 172 | val restDuration = timeoutAt - System.nanoTime 173 | if (restDuration < 0) { 174 | throw new TimeoutException 175 | } 176 | wait(restDuration / 1000000, (restDuration % 1000000).toInt) 177 | } 178 | } else { 179 | while (!isCompleted) { 180 | wait() 181 | } 182 | } 183 | this 184 | } 185 | } 186 | 187 | implicit private def catcher: Catcher[Unit] = { 188 | case throwable: Throwable => { 189 | synchronized { 190 | notifyAll() 191 | } 192 | } 193 | } 194 | 195 | for (successValue <- underlying) { 196 | synchronized { 197 | notifyAll() 198 | } 199 | } 200 | } 201 | 202 | /** 203 | * Forwards all [[Future.Stateless]] API to the underlying `scala.Responder`. 204 | */ 205 | final class FromResponder[AwaitResult](underlying: Responder[AwaitResult]) extends Future.Stateless[AwaitResult] { 206 | override def onComplete(body: AwaitResult => TailRec[Unit])( 207 | implicit catcher: Catcher[TailRec[Unit]]): TailRec[Unit] = { 208 | try { 209 | underlying.respond { a => 210 | body(a).result 211 | } 212 | } catch { 213 | case e if catcher.isDefinedAt(e) => 214 | catcher(e).result 215 | } 216 | done(()) 217 | } 218 | } 219 | 220 | /** 221 | * Forwards all `scala.Responder` API to the underlying [[Future.Stateless]]. 222 | */ 223 | final class ToResponder[AwaitResult](underlying: Future.Stateless[AwaitResult])(implicit catcher: Catcher[Unit]) 224 | extends Responder[AwaitResult] { 225 | 226 | override final def respond(handler: AwaitResult => Unit) { 227 | (underlying.onComplete { a => 228 | done(handler(a)) 229 | } { 230 | case e if catcher.isDefinedAt(e) => { 231 | done(catcher(e)) 232 | } 233 | }).result 234 | } 235 | 236 | } 237 | 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.4.1-SNAPSHOT" 2 | --------------------------------------------------------------------------------