From 2b559c5c4f7e8d2410b0dc98796f2f032cd33c24 Mon Sep 17 00:00:00 2001 From: lxohi <16010388+lxohi@users.noreply.github.com> Date: Sat, 1 Jun 2019 15:19:13 +0800 Subject: [PATCH 1/6] .gitignore: temporarily adds .idea --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d180f4bd6..05aac0216 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ local.sbt secret/ +.idea From e64686fdd1a217d2778b8bffd26871f8e48772ec Mon Sep 17 00:00:00 2001 From: lxohi <16010388+lxohi@users.noreply.github.com> Date: Sat, 1 Jun 2019 17:36:46 +0800 Subject: [PATCH 2/6] create keywords-Defer sub-project --- build.sbt | 13 +++++++++++++ keywords-Defer/.js/build.sbt | 1 + keywords-Defer/.jvm/build.sbt | 1 + keywords-Defer/build.sbt | 1 + 4 files changed, 16 insertions(+) create mode 100644 keywords-Defer/.js/build.sbt create mode 100644 keywords-Defer/.jvm/build.sbt create mode 100644 keywords-Defer/build.sbt diff --git a/build.sbt b/build.sbt index dbe57cbd8..18bfbbdb2 100644 --- a/build.sbt +++ b/build.sbt @@ -25,6 +25,7 @@ lazy val `domains-task` = .dependsOn(`keywords-Shift`, `keywords-Fork` % Test, `keywords-Using` % Test, + `keywords-Defer` % Test, `keywords-Yield` % Test, `comprehension` % Test) lazy val `domains-taskJS` = `domains-task`.js @@ -121,6 +122,17 @@ lazy val `keywords-Using` = lazy val `keywords-UsingJS` = `keywords-Using`.js lazy val `keywords-UsingJVM` = `keywords-Using`.jvm +lazy val `keywords-Defer` = + crossProject(JSPlatform, JVMPlatform) + .crossType(CrossType.Pure) + .settings( + scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-BangNotation` in Compile).value}""", + scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" + ) + .dependsOn(Dsl, `keywords-Shift`, `keywords-Catch`) +lazy val `keywords-DeferJS` = `keywords-Defer`.js +lazy val `keywords-DeferJVM` = `keywords-Defer`.jvm + lazy val `keywords-Catch` = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) @@ -315,6 +327,7 @@ lazy val `package` = project `keywords-AwaitJVM`, `keywords-AsynchronousIoJVM`, `keywords-UsingJVM`, + `keywords-DeferJVM`, `keywords-MapJVM`, `keywords-FlatMapJVM`, `keywords-WithFilterJVM`, diff --git a/keywords-Defer/.js/build.sbt b/keywords-Defer/.js/build.sbt new file mode 100644 index 000000000..5e9154127 --- /dev/null +++ b/keywords-Defer/.js/build.sbt @@ -0,0 +1 @@ +libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.6-SNAP2" % Test diff --git a/keywords-Defer/.jvm/build.sbt b/keywords-Defer/.jvm/build.sbt new file mode 100644 index 000000000..5e9154127 --- /dev/null +++ b/keywords-Defer/.jvm/build.sbt @@ -0,0 +1 @@ +libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.6-SNAP2" % Test diff --git a/keywords-Defer/build.sbt b/keywords-Defer/build.sbt new file mode 100644 index 000000000..5e9154127 --- /dev/null +++ b/keywords-Defer/build.sbt @@ -0,0 +1 @@ +libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.6-SNAP2" % Test From 3b0d258cb637a67eab2ec8377a75f430ecff63aa Mon Sep 17 00:00:00 2001 From: lxohi <16010388+lxohi@users.noreply.github.com> Date: Sat, 1 Jun 2019 17:36:55 +0800 Subject: [PATCH 3/6] create Defer.scala --- .../com/thoughtworks/dsl/keywords/Defer.scala | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala diff --git a/keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala b/keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala new file mode 100644 index 000000000..b9e93522f --- /dev/null +++ b/keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala @@ -0,0 +1,78 @@ +package com.thoughtworks.dsl.keywords + +import com.thoughtworks.dsl.Dsl +import com.thoughtworks.dsl.Dsl.{!!, Keyword} +import com.thoughtworks.dsl.keywords.Catch.DslCatch +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.language.implicitConversions +import scala.util.control.NonFatal + +/** This [[Defer]] keyword automatically manage resources in [[scala.concurrent.Future]], [[domains.task.Task]], + * and other asynchrounous domains derived from `Future` or `Task`. + * + * @see [[dsl]] for usage of this [[Defer]] keyword in continuations + */ +final case class Defer(op: () => Unit) extends AnyVal with Keyword[Defer, Unit] + +trait LowPriorityDefer { + + implicit def deferDsl[Domain]: Dsl[Defer, Domain, Unit] = + new Dsl[Defer, Domain, Unit] { + def cpsApply(keyword: Defer, handler: Unit => Domain): Domain = { + try { + handler(()) + } finally { + keyword.op() + } + } + } + +} + +object Defer extends LowPriorityDefer { + + implicit def implicitDefer(r: => Unit): Defer = Defer(r _) + + def apply(r: => Unit)( + implicit dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit): Defer = new Defer(r _) + + + implicit def throwableContinuationDeferDsl[Domain, Value, R <: AutoCloseable]( + implicit catchDsl: DslCatch[Domain, Domain, Value], + shiftDsl: Dsl[Shift[Domain, Value], Domain, Value] + ): Dsl[Defer, Domain !! Value, Unit] = + new Dsl[Defer, Domain !! Value, Unit] { + def cpsApply(keyword: Defer, handler: Unit => Domain !! Value): Domain !! Value = _ { + try { + !Shift(handler(())) + } finally { + keyword.op() + } + } + } + + implicit def scalaFutureDeferDsl[A]( + implicit executionContext: ExecutionContext): Dsl[Defer, Future[A], Unit] = + new Dsl[Defer, Future[A], Unit] { + def cpsApply(keyword: Defer, handler: Unit => Future[A]): Future[A] = { + val f = try { + handler(()) + } catch { + case NonFatal(e) => + Future.failed(e) + } + val p = Promise[A]() + f.onComplete { t => + try { + keyword.op() + p.complete(t) + } catch { + case NonFatal(e) => + p.failure(e) + } + } + p.future + } + } + +} From da7a430faaf50bc340a31e1c0903340a8f23c743 Mon Sep 17 00:00:00 2001 From: lxohi <16010388+lxohi@users.noreply.github.com> Date: Sat, 1 Jun 2019 17:41:37 +0800 Subject: [PATCH 4/6] README.md: add description of Defer keyword --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 76848dae7..6ab8b0c87 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ We also provide some built-in keywords, including: * The `Fork` keyword for duplicating current context, similar to the `fork` system call in POSIX. * The `Return` keyword for early returning, similar to the native `return` keyword in Scala. * The `Using` keyword to automatically close resources when exiting a scope, similar to the native `using` keyword in C#. + * The `Defer` keyword for automatically run deferred statement when the surrounding function returns, similar to the native `defer` keyword in Go. * The `Monadic` keyword for creating Scalaz or Cats monadic control flow, similar to the !-notation in Idris. * The `NullSafe` keyword for the null safe operator, similar to the `?` operator in Kotlin and Groovy. * The `NoneSafe` keyword for the `None` safe operator, similar to the `Maybe` monad in Haskell. From 9dca0ffe446e940de182b4a486103c1bd60d2651 Mon Sep 17 00:00:00 2001 From: lxohi <16010388+lxohi@users.noreply.github.com> Date: Wed, 5 Jun 2019 18:41:09 +0800 Subject: [PATCH 5/6] Allow Using in plain functions --- keywords-Using/build.sbt | 1 + .../com/thoughtworks/dsl/keywords/Using.scala | 18 ++- .../thoughtworks/dsl/keywords/UsingSpec.scala | 121 ++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 keywords-Using/src/test/scala/com/thoughtworks/dsl/keywords/UsingSpec.scala diff --git a/keywords-Using/build.sbt b/keywords-Using/build.sbt index 8eefca2fd..18caacb90 100644 --- a/keywords-Using/build.sbt +++ b/keywords-Using/build.sbt @@ -21,3 +21,4 @@ scalacOptions ++= { } libraryDependencies += "com.thoughtworks.dsl" %%% "keywords-await" % "1.3.1" % Test +libraryDependencies += "com.thoughtworks.dsl" %%% "domains-task" % "1.3.1" % Test diff --git a/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala b/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala index c8473846a..81221e08e 100644 --- a/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala +++ b/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala @@ -17,7 +17,23 @@ import scala.util.control.NonFatal */ final case class Using[R <: AutoCloseable](open: () => R) extends AnyVal with Keyword[Using[R], R] -object Using { +trait LowPriorityUsing { + + implicit def usingDsl[R <: AutoCloseable, Domain]: Dsl[Using[R], Domain, R] = + new Dsl[Using[R], Domain, R] { + def cpsApply(keyword: Using[R], handler: R => Domain): Domain = { + val r = keyword.open() + try { + handler(r) + } finally { + r.close() + } + } + } + +} + +object Using extends LowPriorityUsing { implicit def implicitUsing[R <: AutoCloseable](r: => R): Using[R] = Using[R](r _) diff --git a/keywords-Using/src/test/scala/com/thoughtworks/dsl/keywords/UsingSpec.scala b/keywords-Using/src/test/scala/com/thoughtworks/dsl/keywords/UsingSpec.scala new file mode 100644 index 000000000..fb26f03ab --- /dev/null +++ b/keywords-Using/src/test/scala/com/thoughtworks/dsl/keywords/UsingSpec.scala @@ -0,0 +1,121 @@ +package com.thoughtworks.dsl.keywords + +import java.util.concurrent.LinkedBlockingQueue + +import org.scalatest.{FreeSpec, Matchers} +import com.thoughtworks.dsl.domains.task.Task +import scala.concurrent.duration._ + +class UsingSpec extends FreeSpec with Matchers { + + "scopeExit" - { + import Using.scopeExit + + "execute sequence: function" in { + val queue = new LinkedBlockingQueue[Int]() + + def run() = { + !scopeExit(() => queue.offer(1)) + queue.offer(2) + } + + run() + + queue.poll() should be(2) + queue.poll() should be(1) + } + + "execute sequence: function contains call-by-name function call" in { + val queue = new LinkedBlockingQueue[Int]() + + def runOp(op: => Unit): Unit = op + + def run() = { + !scopeExit(() => queue.offer(1)) + runOp { + !scopeExit(() => queue.offer(3)) + } + queue.offer(2) + } + + run() + + queue.poll() should be(3) + queue.poll() should be(2) + queue.poll() should be(1) + } + + "execute sequence: function contains call of function accepts function literal execute sequence" in { + val queue = new LinkedBlockingQueue[Int]() + + def runOp(op: () => Unit): Unit = op() + + def run() = { + !scopeExit(() => queue.offer(1)) + runOp(() => !scopeExit(() => queue.offer(3))) + queue.offer(2) + } + + run() + + queue.poll() should be(3) + queue.poll() should be(2) + queue.poll() should be(1) + } + + "execute sequence: task" in { + val queue = new LinkedBlockingQueue[Int]() + + def run() = Task { + !scopeExit(() => queue.offer(1)) + queue.offer(2) + } + + Task.blockingAwait(run(), 1.minute) + + queue.poll() should be(2) + queue.poll() should be(1) + } + + + "execute sequence: task contains call-by-name function call" in { + val queue = new LinkedBlockingQueue[Int]() + + def runTask(op: => Task[Unit]): Unit = Task.blockingAwait(op, 1.minute) + + def run() = Task { + !scopeExit(() => queue.offer(1)) + runTask { + Task(!scopeExit(() => queue.offer(3))) + } + queue.offer(2) + } + + Task.blockingAwait(run(), 1.minute) + + queue.poll() should be(3) + queue.poll() should be(2) + queue.poll() should be(1) + } + + "execute sequence: task contains call of function accepts function literal execute sequence" in { + val queue = new LinkedBlockingQueue[Int]() + + def runOp(op: () => Task[Unit]): Unit = Task.blockingAwait(op(), 1.minute) + + def run() = { + !scopeExit(() => queue.offer(1)) + runOp(() => Task(!scopeExit(() => queue.offer(3)))) + queue.offer(2) + } + + run() + + queue.poll() should be(3) + queue.poll() should be(2) + queue.poll() should be(1) + } + + } + +} From adf41fe80e328cca6f96ca36ad721062565d208a Mon Sep 17 00:00:00 2001 From: lxohi <16010388+lxohi@users.noreply.github.com> Date: Wed, 5 Jun 2019 18:43:57 +0800 Subject: [PATCH 6/6] remove Defer --- README.md | 1 - build.sbt | 13 ---- keywords-Defer/.js/build.sbt | 1 - keywords-Defer/.jvm/build.sbt | 1 - keywords-Defer/build.sbt | 1 - .../com/thoughtworks/dsl/keywords/Defer.scala | 78 ------------------- 6 files changed, 95 deletions(-) delete mode 100644 keywords-Defer/.js/build.sbt delete mode 100644 keywords-Defer/.jvm/build.sbt delete mode 100644 keywords-Defer/build.sbt delete mode 100644 keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala diff --git a/README.md b/README.md index 6ba2717f1..2d3d82ac9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ We also provide some built-in keywords, including: * The `Fork` keyword for duplicating current context, similar to the `fork` system call in POSIX. * The `Return` keyword for early returning, similar to the native `return` keyword in Scala. * The `Using` keyword to automatically close resources when exiting a scope, similar to the native `using` keyword in C#. - * The `Defer` keyword for automatically run deferred statement when the surrounding function returns, similar to the native `defer` keyword in Go. * The `Monadic` keyword for creating Scalaz or Cats monadic control flow, similar to the !-notation in Idris. * The `NullSafe` keyword for the null safe operator, similar to the `?` operator in Kotlin and Groovy. * The `NoneSafe` keyword for the `None` safe operator, similar to the `Maybe` monad in Haskell. diff --git a/build.sbt b/build.sbt index 18bfbbdb2..dbe57cbd8 100644 --- a/build.sbt +++ b/build.sbt @@ -25,7 +25,6 @@ lazy val `domains-task` = .dependsOn(`keywords-Shift`, `keywords-Fork` % Test, `keywords-Using` % Test, - `keywords-Defer` % Test, `keywords-Yield` % Test, `comprehension` % Test) lazy val `domains-taskJS` = `domains-task`.js @@ -122,17 +121,6 @@ lazy val `keywords-Using` = lazy val `keywords-UsingJS` = `keywords-Using`.js lazy val `keywords-UsingJVM` = `keywords-Using`.jvm -lazy val `keywords-Defer` = - crossProject(JSPlatform, JVMPlatform) - .crossType(CrossType.Pure) - .settings( - scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-BangNotation` in Compile).value}""", - scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" - ) - .dependsOn(Dsl, `keywords-Shift`, `keywords-Catch`) -lazy val `keywords-DeferJS` = `keywords-Defer`.js -lazy val `keywords-DeferJVM` = `keywords-Defer`.jvm - lazy val `keywords-Catch` = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) @@ -327,7 +315,6 @@ lazy val `package` = project `keywords-AwaitJVM`, `keywords-AsynchronousIoJVM`, `keywords-UsingJVM`, - `keywords-DeferJVM`, `keywords-MapJVM`, `keywords-FlatMapJVM`, `keywords-WithFilterJVM`, diff --git a/keywords-Defer/.js/build.sbt b/keywords-Defer/.js/build.sbt deleted file mode 100644 index 5e9154127..000000000 --- a/keywords-Defer/.js/build.sbt +++ /dev/null @@ -1 +0,0 @@ -libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.6-SNAP2" % Test diff --git a/keywords-Defer/.jvm/build.sbt b/keywords-Defer/.jvm/build.sbt deleted file mode 100644 index 5e9154127..000000000 --- a/keywords-Defer/.jvm/build.sbt +++ /dev/null @@ -1 +0,0 @@ -libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.6-SNAP2" % Test diff --git a/keywords-Defer/build.sbt b/keywords-Defer/build.sbt deleted file mode 100644 index 5e9154127..000000000 --- a/keywords-Defer/build.sbt +++ /dev/null @@ -1 +0,0 @@ -libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.6-SNAP2" % Test diff --git a/keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala b/keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala deleted file mode 100644 index b9e93522f..000000000 --- a/keywords-Defer/src/main/scala/com/thoughtworks/dsl/keywords/Defer.scala +++ /dev/null @@ -1,78 +0,0 @@ -package com.thoughtworks.dsl.keywords - -import com.thoughtworks.dsl.Dsl -import com.thoughtworks.dsl.Dsl.{!!, Keyword} -import com.thoughtworks.dsl.keywords.Catch.DslCatch -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.language.implicitConversions -import scala.util.control.NonFatal - -/** This [[Defer]] keyword automatically manage resources in [[scala.concurrent.Future]], [[domains.task.Task]], - * and other asynchrounous domains derived from `Future` or `Task`. - * - * @see [[dsl]] for usage of this [[Defer]] keyword in continuations - */ -final case class Defer(op: () => Unit) extends AnyVal with Keyword[Defer, Unit] - -trait LowPriorityDefer { - - implicit def deferDsl[Domain]: Dsl[Defer, Domain, Unit] = - new Dsl[Defer, Domain, Unit] { - def cpsApply(keyword: Defer, handler: Unit => Domain): Domain = { - try { - handler(()) - } finally { - keyword.op() - } - } - } - -} - -object Defer extends LowPriorityDefer { - - implicit def implicitDefer(r: => Unit): Defer = Defer(r _) - - def apply(r: => Unit)( - implicit dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit): Defer = new Defer(r _) - - - implicit def throwableContinuationDeferDsl[Domain, Value, R <: AutoCloseable]( - implicit catchDsl: DslCatch[Domain, Domain, Value], - shiftDsl: Dsl[Shift[Domain, Value], Domain, Value] - ): Dsl[Defer, Domain !! Value, Unit] = - new Dsl[Defer, Domain !! Value, Unit] { - def cpsApply(keyword: Defer, handler: Unit => Domain !! Value): Domain !! Value = _ { - try { - !Shift(handler(())) - } finally { - keyword.op() - } - } - } - - implicit def scalaFutureDeferDsl[A]( - implicit executionContext: ExecutionContext): Dsl[Defer, Future[A], Unit] = - new Dsl[Defer, Future[A], Unit] { - def cpsApply(keyword: Defer, handler: Unit => Future[A]): Future[A] = { - val f = try { - handler(()) - } catch { - case NonFatal(e) => - Future.failed(e) - } - val p = Promise[A]() - f.onComplete { t => - try { - keyword.op() - p.complete(t) - } catch { - case NonFatal(e) => - p.failure(e) - } - } - p.future - } - } - -}