Skip to content

Commit 35ba150

Browse files
committed
added IO examples
1 parent ce02ade commit 35ba150

File tree

16 files changed

+675
-51
lines changed

16 files changed

+675
-51
lines changed

build.sbt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
import scala.scalanative.build._
33

44

5-
ThisBuild/version := "0.1.0"
5+
ThisBuild/version := "0.2.0"
66
ThisBuild/versionScheme := Some("semver-spec")
77
ThisBuild/resolvers ++= Opts.resolver.sonatypeOssSnapshots
88

99

1010

1111
val sharedSettings = Seq(
1212
organization := "com.github.rssh",
13-
scalaVersion := "3.3.4",
13+
scalaVersion := "3.6.4-RC1-bin-SNAPSHOT",
14+
//scalaVersion := "3.3.4",
1415
scalacOptions ++= Seq(
1516
"-Xcheck-macros",
17+
"--color", "never",
1618
),
1719
libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0" % Test
1820
)
@@ -59,5 +61,8 @@ lazy val taglessFinal = crossProject(JSPlatform, JVMPlatform, NativePlatform)
5961
.settings(
6062
name := "appcontext-tf",
6163
libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.23" % "optional"
62-
)
64+
).jvmSettings(
65+
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.7" % "test",
66+
libraryDependencies += "com.github.rssh" %% "cps-async-connect-cats-effect" % "0.9.23" % "test",
67+
)
6368

core/shared/src/main/scala/com/github/rssh/appcontext/AppContextProviders.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ object AppContextProviders {
7171

7272

7373
inline def checkAllAreNeeded[Xs<:NonEmptyTuple](using p: AppContextProviders[Xs]): Boolean =
74-
${ checkAllAreNeededImpl[Xs]('p) }
74+
${ TupleMacroses.checkAllAreNeeded[[X]=>>AppContextProvider[X],[X<:NonEmptyTuple]=>>AppContextProviders[X],Xs]('p) }
75+
// ${ checkAllAreNeededImpl[Xs]('p) }
7576

77+
// not used,
7678
private def checkAllAreNeededImpl[T<:NonEmptyTuple:Type](p: Expr[AppContextProviders[T]])(using Quotes): Expr[Boolean] = {
7779
import quotes.reflect.*
7880
val tupleType = TypeRepr.of[T]

core/shared/src/main/scala/com/github/rssh/appcontext/TupleMacroses.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,41 @@ object TupleMacroses {
1717
report.throwError(s"Type ${t.show} is not a tuple")
1818
}
1919

20+
def checkAllAreNeeded[P[_]:Type, Ps[_<:NonEmptyTuple]:Type, Xs <: NonEmptyTuple : Type](using Quotes)(p: Expr[Ps[Xs]]): Expr[Boolean] = {
21+
import quotes.reflect.*
22+
val tupleType = TypeRepr.of[Xs]
23+
val tupleTypes = TupleMacroses.extractTupleTypes(tupleType)
24+
//val fromProvidersSymbol = Symbol.requiredMethod("com.github.rssh.appcontext.AppContextProvider.fromProviders")
25+
val fromProvidersSymbols = TypeRepr.of[P].typeSymbol.companionClass.methodMember("fromProviders")
26+
if (fromProvidersSymbols.isEmpty) then
27+
println("TypeRepr.of[P]: "+TypeRepr.of[P].show)
28+
println(s"TypeRepr.of[P].typeSymbol: ${TypeRepr.of[P].typeSymbol}")
29+
println(s"TypeRepr.of[P].typeSymbol.companionClass: ${TypeRepr.of[P].typeSymbol.companionClass}")
30+
println(s"TypeRepr.of[P].typeSymbol.companionClass.methodMembers: ${TypeRepr.of[P].typeSymbol.companionClass.methodMembers}")
31+
32+
33+
34+
report.throwError(s"Cannot find fromProviders method in ${TypeRepr.of[P].show}")
35+
val fromProvidersSymbol = fromProvidersSymbols.head
36+
var retval = true
37+
for {t <- tupleTypes} {
38+
val tProvider = TypeRepr.of[P].appliedTo(t)
39+
Implicits.search(tProvider) match
40+
case failure: ImplicitSearchFailure =>
41+
// impossible, it will be success.
42+
report.error(s"Cannot find ${tProvider.show}: ${failure.explanation}")
43+
case success: ImplicitSearchSuccess =>
44+
// if this is 'this ?
45+
success.tree match
46+
case Apply(TypeApply(fun, targs), args@List(p1, ti)) if (fun.symbol == fromProvidersSymbol) =>
47+
if !(p1.tpe =:= p.asTerm.tpe) then
48+
report.error(s"Provider for ${t.show} in ${p.show} is not necessory: ${success.tree.show} provider will be used")
49+
retval = false
50+
case _ =>
51+
report.error(s"Provider for ${t.show} is not needed in ${p.show}, it will be resolved as: ${success.tree.show}")
52+
retval = false
53+
}
54+
Expr(retval)
55+
}
56+
2057
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.github.rssh.appcontexttest
2+
3+
4+
import cats.effect.*
5+
import cats.syntax.all.*
6+
import com.github.rssh.appcontext.*
7+
import cps.*
8+
import cps.monads.catsEffect.{given,*}
9+
10+
11+
12+
object Example4TestTF {
13+
14+
type IOResource = [X]=>>Resource[IO,X]
15+
16+
type Sql = String
17+
extension (sc: StringContext)
18+
def sql(args: Any*): Sql = sc.s(args *)
19+
20+
trait Connection extends AutoCloseable {
21+
def execute[F[_]:Async](sql: Sql): F[String]
22+
}
23+
object Connection {
24+
given AppContextAsyncProvider[IOResource,Connection] with {
25+
def get: Resource[IO,Connection] = DBPool.connection
26+
}
27+
}
28+
29+
object DBPool {
30+
def connection: Resource[IO, Connection] = Resource.make(IO(new Connection {
31+
def execute[F[_]:Async](sql: String): F[String] = summon[Async[F]].pure(s"result of ${sql}")
32+
def close(): Unit = println("close connection")
33+
}))(cn => { IO.delay(cn.close()) })
34+
}
35+
36+
case class User(name: String, email: String)
37+
38+
trait UserDatabase {
39+
def insert[F[_]:InAppContext[Connection *: EmptyTuple]:Async](user: User): F[User]
40+
}
41+
object UserDatabase {
42+
43+
given AppContextProvider[UserDatabase] with {
44+
val v = new UserDatabase {
45+
def insert[F[_]:InAppContext[Connection *: EmptyTuple]:Async](user: User): F[User] = {
46+
for{
47+
conn <- InAppContext.get[F, Connection]
48+
_ <- conn.execute(sql"insert into subscribers(name, email) values ${user.name} ${user.email}")
49+
} yield user
50+
}
51+
}
52+
override def get: UserDatabase = v
53+
}
54+
55+
}
56+
57+
trait EmailSender {
58+
def sendEmail[F[_]:Async](email: String, subject: String, body: String): F[Unit]
59+
}
60+
61+
object EmailSender {
62+
given AppContextProvider[EmailSender] with {
63+
val v = new EmailSender {
64+
def sendEmail[F[_]:Async](email: String, subject: String, body: String): F[Unit] = {
65+
summon[Async[F]].pure(println(s"send email to ${email} with subject ${subject} and body ${body}"))
66+
}
67+
}
68+
override def get: EmailSender = v
69+
}
70+
}
71+
72+
class SubscriptionService {
73+
def subscribe[F[_]:InAppContext[(UserDatabase, EmailSender, Connection)]:Async](user: User): F[Unit] = {
74+
for {
75+
userDb <- InAppContext.get[F, UserDatabase]
76+
emailSender <- InAppContext.get[F, EmailSender]
77+
_ <- userDb.insert[F](user)
78+
_ <- emailSender.sendEmail[F](user.email, "Welcome", "Welcome to our service")
79+
} yield ()
80+
}
81+
}
82+
83+
84+
}
85+
86+
class Example4Test extends munit.FunSuite {
87+
88+
import Example4TestTF.*
89+
90+
test("insert user") {
91+
val userDatabase = summon[AppContextProvider[UserDatabase]].get
92+
val user = User("John", "[email protected]")
93+
val program = userDatabase.insert[IOResource](user)
94+
import cats.effect.unsafe.implicits.global
95+
program.use{ result =>
96+
IO {
97+
assert(result == user)
98+
}
99+
}.unsafeRunSync()
100+
}
101+
102+
test("new subscriber ") {
103+
104+
val subscriptionService = SubscriptionService()
105+
106+
val user = User("John", "[email protected]")
107+
val program = subscriptionService.subscribe[IOResource](user)
108+
import cats.effect.unsafe.implicits.global
109+
program.use{ _ =>
110+
IO {
111+
assert(true)
112+
}
113+
}.unsafeRunSync()
114+
115+
116+
}
117+
118+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.github.rssh.appcontexttest
2+
3+
4+
import cats.effect.*
5+
import cats.syntax.all.*
6+
import com.github.rssh.appcontext.{InAppContext, *}
7+
import cps.*
8+
import cps.monads.catsEffect.{*, given}
9+
10+
11+
12+
object Example5TestTF {
13+
14+
type IOResource = [X]=>>Resource[IO,X]
15+
16+
type Sql = String
17+
extension (sc: StringContext)
18+
def sql(args: Any*): Sql = sc.s(args *)
19+
20+
trait Connection extends AutoCloseable {
21+
def execute[F[_]:Async](sql: Sql): F[String]
22+
}
23+
24+
object Connection {
25+
given AppContextAsyncProvider[IOResource,Connection] with {
26+
def get: Resource[IO,Connection] = DBPool.connection
27+
}
28+
}
29+
30+
object DBPool {
31+
def connection: Resource[IO, Connection] = Resource.make(IO(new Connection {
32+
def execute[F[_]:Async](sql: String): F[String] = summon[Async[F]].pure(s"result of ${sql}")
33+
def close(): Unit = println("close connection")
34+
}))(cn => { IO.delay(cn.close()) })
35+
}
36+
37+
case class User(name: String, email: String)
38+
39+
trait UserDatabase[F[_]:InAppContext[Connection *: EmptyTuple]:Async] {
40+
def insert(user: User): F[User]
41+
}
42+
object UserDatabase {
43+
44+
given [F[_]:InAppContext[Connection *: EmptyTuple]:Async]: AppContextProvider[UserDatabase[F]] with {
45+
val v = new UserDatabase[F] {
46+
def insert(user: User): F[User] = {
47+
for{
48+
conn <- InAppContext.get[F, Connection]
49+
_ <- conn.execute(sql"insert into subscribers(name, email) values ${user.name} ${user.email}")
50+
} yield user
51+
}
52+
}
53+
override def get: UserDatabase[F] = v
54+
}
55+
56+
}
57+
58+
trait EmailSender {
59+
def sendEmail[F[_]:Async](email: String, subject: String, body: String): F[Unit]
60+
}
61+
62+
object EmailSender {
63+
given AppContextProvider[EmailSender] with {
64+
val v = new EmailSender {
65+
def sendEmail[F[_]:Async](email: String, subject: String, body: String): F[Unit] = {
66+
summon[Async[F]].pure(println(s"send email to ${email} with subject ${subject} and body ${body}"))
67+
}
68+
}
69+
override def get: EmailSender = v
70+
}
71+
}
72+
73+
class SubscriptionService[F[_]:InAppContext[(UserDatabase[F], EmailSender)]:Async] {
74+
def subscribe(user: User): F[Unit] = {
75+
for {
76+
userDb <- InAppContext.get[F, UserDatabase[F]]
77+
emailSender <- InAppContext.get[F, EmailSender]
78+
_ <- userDb.insert(user)
79+
_ <- emailSender.sendEmail[F](user.email, "Welcome", "Welcome to our service")
80+
} yield ()
81+
}
82+
}
83+
84+
85+
}
86+
87+
class Example5Test extends munit.FunSuite {
88+
89+
import Example5TestTF.*
90+
91+
type IOResource = [X]=>>Resource[IO,X]
92+
93+
test("insert user") {
94+
95+
96+
val userDatabase = summon[AppContextProvider[UserDatabase[IOResource]]].get
97+
98+
val user = User("John", "[email protected]")
99+
//implicit val printCode = cps.macros.flags.PrintCode
100+
//implicit val printTree = cps.macros.flags.PrintTree
101+
val program = userDatabase.insert(user)
102+
import cats.effect.unsafe.implicits.global
103+
program.use{ result =>
104+
IO {
105+
assert(result == user)
106+
}
107+
}.unsafeRunSync()
108+
}
109+
110+
test("subscribe user ") {
111+
val subscriptionService = SubscriptionService[IOResource]()
112+
val user = User("John", "[email protected]")
113+
val program = subscriptionService.subscribe(user)
114+
import cats.effect.unsafe.implicits.global
115+
program.use{
116+
_ => IO {
117+
assert(true)
118+
}
119+
}.unsafeRunSync()
120+
121+
122+
123+
}
124+
125+
}
126+

0 commit comments

Comments
 (0)