diff --git a/README.md b/README.md index c47f2d95..bcf41272 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ - -## Gopher: asynchronous implementation of go-like channels/selectors in scala +# πŸ‡ΊπŸ‡¦ HELP UKRAINE + +I'm the creator of this project. +My country, Ukraine, [is being invaded by the Russian Federation, right now](https://war.ukraine.ua). If you want to help my country to fight, consider donating to [charity supporting Ukrainian army](https://www.comebackalive.in.ua/). More options is described on [support ukraine](https://supportukrainenow.org/) site. + +# Gopher: asynchronous implementation of go-like channels/selectors in scala ======= ### Dependences: For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.1" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "4.0.2" For scala 3 and 3.1.0: diff --git a/build.sbt b/build.sbt index 6a268359..ce5add4a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,17 +1,18 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.1" +val dottyVersion = "3.3.5" +//val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.1" +ThisBuild/version := "4.0.7" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", - resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.7", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, + //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", + libraryDependencies += "io.github.dotty-cps-async" %%% "dotty-cps-async" % "1.0.2", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.4" % Test, ) lazy val root = project @@ -26,14 +27,16 @@ lazy val root = project ).enablePlugins(GhpagesPlugin, SiteScaladocPlugin) - +// for scala-native support we need munit lazy val gopher = crossProject(JSPlatform, JVMPlatform) .in(file(".")) .settings(sharedSettings) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), + //scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), + // Error in dotty + scalacOptions ++= Seq( "-unchecked", "-Xprint:types" ), fork := true, /* javaOptions ++= Seq( @@ -45,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.0" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.5" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/build.properties b/project/build.properties index bb3a9b7d..081fdbbc 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.6 +sbt.version=1.10.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index 0b41d0fd..3c903831 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,8 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index a20ef958..e02ae609 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -36,7 +36,7 @@ trait ReadChannel[F[_], A]: def addDoneReader(reader: Reader[Unit]): Unit - lazy val done: ReadChannel[F,Unit] = DoneReadChannel() + final lazy val done: ReadChannel[F,Unit] = DoneReadChannel() type done = Unit @@ -52,12 +52,13 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read()(using CpsMonadContext[F]): A = await(aread())(using rAsyncMonad) + transparent inline def read[G[_]]()(using mc:CpsMonadContext[G], fg:CpsMonadConversion[F,G]): A = + await(aread()) /** * Synonim for read. */ - transparent inline def ?(using CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad) + transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread()) /** * return F which contains sequence from first `n` elements. @@ -84,7 +85,7 @@ trait ReadChannel[F[_], A]: * should be called inside async block. **/ transparent inline def take(n: Int)(using CpsMonadContext[F]): IndexedSeq[A] = - await(atake(n))(using rAsyncMonad) + await(atake(n)) /** * read value and return future with @@ -107,7 +108,7 @@ trait ReadChannel[F[_], A]: * * should be called inside async block. **/ - transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead())(using rAsyncMonad) + transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead()) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad @@ -131,7 +132,7 @@ trait ReadChannel[F[_], A]: * until end of stream is not reached **/ transparent inline def foreach(inline f: A=>Unit)(using CpsMonadContext[F]): Unit = - await(aforeach(f))(using rAsyncMonad) + await(aforeach(f)) def map[B](f: A=>B): ReadChannel[F,B] = @@ -171,7 +172,7 @@ trait ReadChannel[F[_], A]: } transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S )(using mc:CpsMonadContext[F]): S = - await[F,S,F](afold(s0)(f))(using rAsyncMonad, mc) + await[F,S,F](afold(s0)(f)) def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index fb3d93fe..4ca37b84 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -67,7 +67,7 @@ class Select[F[_]](api: Gopher[F]): } } - transparent inline def afold[S](s0:S)(inline step: S => S | SelectFold.Done[S]) : F[S] = + transparent inline def afold[S](s0:S)(inline step: CpsMonadContext[F] ?=> S => S | SelectFold.Done[S]) : F[S] = given CpsAsyncMonad[F] = api.asyncMonad async[F]{ fold(s0)(step) diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index 14440418..c3de322b 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -16,7 +16,7 @@ trait SelectListeners[F[_],S, R]: def runAsync():F[R] - transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync())(using asyncMonad) + transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync()) diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 23639188..fd93363c 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -13,7 +13,7 @@ import scala.util.control.NonFatal object SelectMacro: - import cps.macros.forest.TransformUtil + import cps.macros.common.TransformUtil sealed trait SelectGroupExpr[F[_],S, R]: def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] @@ -56,7 +56,7 @@ object SelectMacro: )(using Quotes): Expr[R] = val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad, $monadContext) } + val r = '{ await($g.runAsync())(using $monadContext, CpsMonadConversion.identityConversion[F]) } r.asExprOf[R] def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 758455f8..a98c0960 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -23,13 +23,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) @targetName("write1") - transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) @targetName("write2") - transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) //def Write(x:A):WritePattern = new WritePattern(x) @@ -52,7 +52,7 @@ trait WriteChannel[F[_], A]: } transparent inline def writeAll(inline collection: IterableOnce[A])(using mc: CpsMonadContext[F]): Unit = - await(awriteAll(collection))(using asyncMonad, mc) + await(awriteAll(collection)) def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index a29d9501..24f17f01 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,7 +6,8 @@ import cps._ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[[A] =>> ReadChannel[F,A]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with + +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsPureMonadInstanceContext[[A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = diff --git a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala index 8760ce74..7cfc1338 100644 --- a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala +++ b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala @@ -8,8 +8,8 @@ import scala.concurrent.* import scala.concurrent.duration.* import scala.collection.SortedSet -import cps.monads.FutureAsyncMonad -import gopher.monads.given +import cps.monads.{given,*} +import gopher.monads.{given,*} class ChannelMonadSuite extends FunSuite { @@ -19,17 +19,21 @@ class ChannelMonadSuite extends FunSuite { test("using channel as monad and read inside") { - val chX = ReadChannel.fromValues(1,2,3,4,5) - val chY = ReadChannel.fromValues(1,2,3,4,5) + + val chX = ReadChannel.fromValues[Future,Int](1,2,3,4,5) + val chY = ReadChannel.fromValues[Future,Int](1,2,3,4,5) - val squares = async[[X] =>> ReadChannel[Future,X]] { - val x = await(chX) + + val squares: ReadChannel[Future,Int] = async[[X] =>> ReadChannel[Future,X]] { + val x: Int = await(chX) //println(s"reading from X $x") - val y = chY.read() + val y: Int = chY.read() //println(s"reading from Y $y") x*y } + + async[Future] { val a1 = squares.read() //println(s"a1==${a1}") @@ -46,6 +50,7 @@ class ChannelMonadSuite extends FunSuite { } } + test("using channel with flatMap") { diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 917b444d..89edc8fc 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -16,29 +16,17 @@ class QueensSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - case class State( - busyRows:Set[Int], - busyColumns:Set[Int], - busyLRDiagonals:Set[Int], - busyRLDiagonals:Set[Int], - queens: Vector[(Int,Int)] - ) { + type State = Vector[Int] + + extension(queens:State) { - def isBusy(i:Int, j:Int): Boolean = - busyRows.contains(i) || - busyColumns.contains(j) || - busyLRDiagonals.contains(i-j) || - busyRLDiagonals.contains(i+j) - - - def put(i:Int, j:Int): State = - copy( busyRows = busyRows + i, - busyColumns = busyColumns + j, - busyLRDiagonals = busyLRDiagonals + (i-j), - busyRLDiagonals = busyRLDiagonals + (i+j), - queens = queens :+ (i,j) - ) + def isUnderAttack(i:Int, j:Int): Boolean = + queens.zipWithIndex.exists{ (qj,qi) => + qi == i || qj == j || i-j == qi-qj || i+j == qi+qj + } + def asPairs:Vector[(Int,Int)] = + queens.zipWithIndex.map(_.swap) } @@ -47,32 +35,31 @@ class QueensSuite extends FunSuite { def putQueen(state:State): ReadChannel[Future,State] = val ch = makeChannel[State]() async[Future] { - val i = state.queens.length + val i = state.length if i < N then - for{ j <- 0 until N if !state.isBusy(i,j) } - ch.write(state.put(i,j)) + for{ j <- 0 until N if !state.isUnderAttack(i,j) } + ch.write(state appended j) ch.close() } ch def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.queens.size < N) then + if(state.length < N) then val nextState = await(putQueen(state)) await(solutions(nextState)) else state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Vector.empty) test("two first solution for 8 queens problem") { async[Future] { - val r = solutions(emptyState).take(2) + val r = solutions(Vector.empty).take(2) assert(!r.isEmpty) - println(r.map(_.queens)) + println(r.map(_.asPairs)) } } -} \ No newline at end of file +}