From 72704b1da1c0fdb431e3af942cec43eb1dfa7745 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Sun, 31 Dec 2023 18:18:18 +0100 Subject: [PATCH 01/14] write the Prop interface --- .../src/main/scala/cats/effect/std/Prop.scala | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 std/shared/src/main/scala/cats/effect/std/Prop.scala diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala new file mode 100644 index 0000000000..1fdb5363af --- /dev/null +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -0,0 +1,170 @@ +/* + * Copyright 2020-2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect.std + +import cats.{~>, Applicative, Functor} +import cats.data.{EitherT, IorT, Kleisli, OptionT, ReaderWriterStateT, StateT, WriterT} +import cats.effect.kernel.Sync +import cats.kernel.Monoid + +trait Prop[F[_]] { self => + + /** + * Retrieves the value for the specified key. + */ + def get(key: String): F[Option[String]] + + /** + * Replaces the value for the specified key with `value`, returning the previous value. + */ + def getAndSet(key: String, value: String): F[Option[String]] = getAndUpdate(key, _ => value) + + /** + * Updates the value for the specified key using `f` if a previous value for the same key + * exists and returns the previous value. + */ + def getAndUpdate(key: String, f: String => String): F[Option[String]] = + modify(key, a => (f(a), a)) + + /** + * Modifies the value for the specified key only if a previous value for the same key exists. + */ + def modify(key: String, f: String => (String, String)): F[Option[String]] + + /** + * Sets the value for the specified key to `value` disregarding any previous value for the + * same key. + */ + def set(key: String, value: String): F[Unit] + + /** + * Satisfies: `prop.update(key, f) == prop.modify(key, x => (f(x), a)).void` and + * `prop.update(_ => a) == prop.set(a)` + */ + def update(key: String, f: String => String): F[Unit] + + /** + * Updates the value for the specified key using `f` if a previous value for the same key + * exists and returns the updated value. + */ + def updateAndGet(key: String, f: String => String): F[Option[String]] = + modify( + key, + a => { + val newA = f(a) + (newA, newA) + }) + + def mapK[G[_]](f: F ~> G): Prop[G] = new Prop[G] { + def get(key: String): G[Option[String]] = f(self.get(key)) + def modify(key: String, fn: String => (String, String)): G[Option[String]] = f( + self.modify(key, fn)) + def set(key: String, value: String): G[Unit] = f(self.set(key, value)) + def update(key: String, fn: String => String): G[Unit] = f(self.update(key, fn)) + } +} + +object Prop { + + /** + * Summoner method for `Prop` instances. + */ + def apply[F[_]](implicit ev: Prop[F]): ev.type = ev + + /** + * Constructs a `Prop` instance for `F` data types that are [[cats.effect.kernel.Sync]]. + */ + def make[F[_]](implicit F: Sync[F]): Prop[F] = new SyncProp[F] + + /** + * [[Prop]] instance built for `cats.data.EitherT` values initialized with any `F` data type + * that also implements `Prop`. + */ + implicit def catsEitherTProp[F[_]: Prop: Functor, L]: Prop[EitherT[F, L, *]] = + Prop[F].mapK(EitherT.liftK) + + /** + * [[Prop]] instance built for `cats.data.Kleisli` values initialized with any `F` data type + * that also implements `Prop`. + */ + implicit def catsKleisliProp[F[_]: Prop, R]: Prop[Kleisli[F, R, *]] = + Prop[F].mapK(Kleisli.liftK) + + /** + * [[Prop]] instance built for `cats.data.OptionT` values initialized with any `F` data type + * that also implements `Prop`. + */ + implicit def catsOptionTProp[F[_]: Prop: Functor]: Prop[OptionT[F, *]] = + Prop[F].mapK(OptionT.liftK) + + /** + * [[Prop]] instance built for `cats.data.StateT` values initialized with any `F` data type + * that also implements `Prop`. + */ + implicit def catsStateTProp[F[_]: Prop: Applicative, S]: Prop[StateT[F, S, *]] = + Prop[F].mapK(StateT.liftK) + + /** + * [[Prop]] instance built for `cats.data.WriterT` values initialized with any `F` data type + * that also implements `Prop`. + */ + implicit def catsWriterTProp[ + F[_]: Prop: Applicative, + L: Monoid + ]: Prop[WriterT[F, L, *]] = + Prop[F].mapK(WriterT.liftK) + + /** + * [[Prop]] instance built for `cats.data.IorT` values initialized with any `F` data type that + * also implements `Prop`. + */ + implicit def catsIorTProp[F[_]: Prop: Functor, L]: Prop[IorT[F, L, *]] = + Prop[F].mapK(IorT.liftK) + + /** + * [[Prop]] instance built for `cats.data.ReaderWriterStateT` values initialized with any `F` + * data type that also implements `Prop`. + */ + implicit def catsReaderWriterStateTProp[ + F[_]: Prop: Applicative, + E, + L: Monoid, + S + ]: Prop[ReaderWriterStateT[F, E, L, S, *]] = + Prop[F].mapK(ReaderWriterStateT.liftK) + + private[std] final class SyncProp[F[_]](implicit F: Sync[F]) extends Prop[F] { + + def get(key: String): F[Option[String]] = + F.delay(Option(System.getProperty(key))) // thread-safe + + def modify(key: String, f: String => (String, String)): F[Option[String]] = F.delay { + Option(System.getProperty(key)).map { value => + val (newValue, output) = f(value) + System.setProperty(key, newValue) + output + } + } + + def set(key: String, value: String): F[Unit] = + F.void(F.delay(System.setProperty(key, value))) + + def update(key: String, f: String => String): F[Unit] = F.void( + F.delay(Option(System.getProperty(key)).map(value => System.setProperty(key, f(value)))) + ) + } +} From 4b8144a340c776b5e52349b0bfab375dfa8f0611 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Sun, 31 Dec 2023 18:30:47 +0100 Subject: [PATCH 02/14] sbt prePR --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 1fdb5363af..20a8c6a3d1 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Typelevel + * Copyright 2020-2023 Typelevel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 0385959f6a2c765e4a461f33d8a4d68f6e92e32a Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Sun, 31 Dec 2023 19:47:24 +0100 Subject: [PATCH 03/14] add more tests --- .../src/main/scala/cats/effect/IO.scala | 4 +- .../src/main/scala/cats/effect/std/Prop.scala | 8 +++ .../test/scala/cats/effect/std/PropSpec.scala | 60 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/shared/src/test/scala/cats/effect/std/PropSpec.scala diff --git a/core/shared/src/main/scala/cats/effect/IO.scala b/core/shared/src/main/scala/cats/effect/IO.scala index a54724a9e0..2f461b8e5e 100644 --- a/core/shared/src/main/scala/cats/effect/IO.scala +++ b/core/shared/src/main/scala/cats/effect/IO.scala @@ -39,7 +39,7 @@ import cats.data.Ior import cats.effect.instances.spawn import cats.effect.kernel.CancelScope import cats.effect.kernel.GenTemporal.handleDuration -import cats.effect.std.{Backpressure, Console, Env, Supervisor, UUIDGen} +import cats.effect.std.{Backpressure, Console, Env, Prop, Supervisor, UUIDGen} import cats.effect.tracing.{Tracing, TracingEvent} import cats.effect.unsafe.IORuntime import cats.syntax.all._ @@ -1992,6 +1992,8 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits { implicit val envForIO: Env[IO] = Env.make + implicit val propForIO: Prop[IO] = Prop.make + // This is cached as a val to save allocations, but it uses ops from the Async // instance which is also cached as a val, and therefore needs to appear // later in the file diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 20a8c6a3d1..3cbe481aad 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -69,12 +69,18 @@ trait Prop[F[_]] { self => (newA, newA) }) + /** + * Removes the property. + */ + def unset(key: String): F[Unit] + def mapK[G[_]](f: F ~> G): Prop[G] = new Prop[G] { def get(key: String): G[Option[String]] = f(self.get(key)) def modify(key: String, fn: String => (String, String)): G[Option[String]] = f( self.modify(key, fn)) def set(key: String, value: String): G[Unit] = f(self.set(key, value)) def update(key: String, fn: String => String): G[Unit] = f(self.update(key, fn)) + def unset(key: String) = f(self.unset(key)) } } @@ -166,5 +172,7 @@ object Prop { def update(key: String, f: String => String): F[Unit] = F.void( F.delay(Option(System.getProperty(key)).map(value => System.setProperty(key, f(value)))) ) + + def unset(key: String): F[Unit] = F.void(F.delay(System.clearProperty(key))) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala new file mode 100644 index 0000000000..b7be8e3b4a --- /dev/null +++ b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala @@ -0,0 +1,60 @@ +/* + * Copyright 2020-2023 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect +package std + +class PropSpec extends BaseSpec { + + "Prop" should { + "retrieve a property just set" in real { + Prop[IO] + .set("foo", "bar") *> Prop[IO].get("foo").flatMap(x => IO(x mustEqual Some("bar"))) + } + "return none for a non-existent property" in real { + Prop[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) + } + "getAndSet" in real { + for { + _ <- Prop[IO].set("foo", "bar") + getAndSetResult <- Prop[IO].getAndSet("foo", "baz") + getResult <- Prop[IO].get("foo") + } yield { + getAndSetResult mustEqual Some("bar") + getResult mustEqual Some("baz") + } + } + "getAndUpdate" in real { + for { + _ <- Prop[IO].set("foo", "bar") + getAndSetResult <- Prop[IO].getAndUpdate("foo", v => v + "baz") + getResult <- Prop[IO].get("foo") + } yield { + getAndSetResult mustEqual Some("bar") + getResult mustEqual Some("barbaz") + } + } + "unset" in real { + Prop[IO].set("foo", "bar") *> Prop[IO] + .unset("foo") *> Prop[IO].get("foo").flatMap(x => IO(x must beNone)) + } + "not modify anythng if a value for the key does not exist" in real { + Prop[IO].unset("foo") *> Prop[IO].get("foo").flatMap(x => IO(x must beNone)) *> Prop[IO] + .modify("foo", _ => ("new value", "output")) + .flatMap(x => IO(x must beNone)) + } + } +} From 030915f8ff62e905af8ab510b95eb5c36cf55212 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Mon, 1 Jan 2024 11:33:18 +0100 Subject: [PATCH 04/14] randomize the test key --- .../src/main/scala/cats/effect/std/Prop.scala | 1 + .../test/scala/cats/effect/std/PropSpec.scala | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 3cbe481aad..7a8da03319 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -42,6 +42,7 @@ trait Prop[F[_]] { self => /** * Modifies the value for the specified key only if a previous value for the same key exists. + * `f` returns the new value to be set and the return value of `modify`. */ def modify(key: String, f: String => (String, String)): F[Option[String]] diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala index b7be8e3b4a..e5be184a6a 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala @@ -21,40 +21,51 @@ class PropSpec extends BaseSpec { "Prop" should { "retrieve a property just set" in real { - Prop[IO] - .set("foo", "bar") *> Prop[IO].get("foo").flatMap(x => IO(x mustEqual Some("bar"))) + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + Prop[IO].set(key, "bar") *> Prop[IO].get(key).flatMap(x => IO(x mustEqual Some("bar"))) + } } "return none for a non-existent property" in real { Prop[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) } "getAndSet" in real { - for { - _ <- Prop[IO].set("foo", "bar") - getAndSetResult <- Prop[IO].getAndSet("foo", "baz") - getResult <- Prop[IO].get("foo") - } yield { - getAndSetResult mustEqual Some("bar") - getResult mustEqual Some("baz") + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + for { + _ <- Prop[IO].set(key, "bar") + getAndSetResult <- Prop[IO].getAndSet(key, "baz") + getResult <- Prop[IO].get(key) + } yield { + getAndSetResult mustEqual Some("bar") + getResult mustEqual Some("baz") + } } } "getAndUpdate" in real { - for { - _ <- Prop[IO].set("foo", "bar") - getAndSetResult <- Prop[IO].getAndUpdate("foo", v => v + "baz") - getResult <- Prop[IO].get("foo") - } yield { - getAndSetResult mustEqual Some("bar") - getResult mustEqual Some("barbaz") + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + for { + _ <- Prop[IO].set(key, "bar") + getAndSetResult <- Prop[IO].getAndUpdate(key, v => v + "baz") + getResult <- Prop[IO].get(key) + } yield { + getAndSetResult mustEqual Some("bar") + getResult mustEqual Some("barbaz") + } } } "unset" in real { - Prop[IO].set("foo", "bar") *> Prop[IO] - .unset("foo") *> Prop[IO].get("foo").flatMap(x => IO(x must beNone)) + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + Prop[IO].set(key, "bar") *> Prop[IO] + .unset(key) *> Prop[IO].get(key).flatMap(x => IO(x must beNone)) + } } - "not modify anythng if a value for the key does not exist" in real { - Prop[IO].unset("foo") *> Prop[IO].get("foo").flatMap(x => IO(x must beNone)) *> Prop[IO] - .modify("foo", _ => ("new value", "output")) - .flatMap(x => IO(x must beNone)) + "not modify anything if a value for the key does not exist" in real { + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + for { + _ <- Prop[IO].get(key).flatMap(x => IO(x must beNone)) + x <- Prop[IO].modify(key, _ => ("new value", "output")) + assertion <- IO(x must beNone) + } yield assertion + } } } } From fe589e631c82161848fa2df647f1ca5f50fe7616 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Sun, 7 Jan 2024 22:07:11 +0100 Subject: [PATCH 05/14] remove non-atomic functions --- .../src/main/scala/cats/effect/std/Prop.scala | 58 +++---------------- .../test/scala/cats/effect/std/PropSpec.scala | 38 ++---------- 2 files changed, 13 insertions(+), 83 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 7a8da03319..58b17cf17c 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -21,6 +21,8 @@ import cats.data.{EitherT, IorT, Kleisli, OptionT, ReaderWriterStateT, StateT, W import cats.effect.kernel.Sync import cats.kernel.Monoid +import java.util.Properties + trait Prop[F[_]] { self => /** @@ -28,60 +30,24 @@ trait Prop[F[_]] { self => */ def get(key: String): F[Option[String]] - /** - * Replaces the value for the specified key with `value`, returning the previous value. - */ - def getAndSet(key: String, value: String): F[Option[String]] = getAndUpdate(key, _ => value) - - /** - * Updates the value for the specified key using `f` if a previous value for the same key - * exists and returns the previous value. - */ - def getAndUpdate(key: String, f: String => String): F[Option[String]] = - modify(key, a => (f(a), a)) - - /** - * Modifies the value for the specified key only if a previous value for the same key exists. - * `f` returns the new value to be set and the return value of `modify`. - */ - def modify(key: String, f: String => (String, String)): F[Option[String]] - /** * Sets the value for the specified key to `value` disregarding any previous value for the * same key. */ def set(key: String, value: String): F[Unit] - /** - * Satisfies: `prop.update(key, f) == prop.modify(key, x => (f(x), a)).void` and - * `prop.update(_ => a) == prop.set(a)` - */ - def update(key: String, f: String => String): F[Unit] - - /** - * Updates the value for the specified key using `f` if a previous value for the same key - * exists and returns the updated value. - */ - def updateAndGet(key: String, f: String => String): F[Option[String]] = - modify( - key, - a => { - val newA = f(a) - (newA, newA) - }) - /** * Removes the property. */ def unset(key: String): F[Unit] + def entries: F[Properties] + def mapK[G[_]](f: F ~> G): Prop[G] = new Prop[G] { def get(key: String): G[Option[String]] = f(self.get(key)) - def modify(key: String, fn: String => (String, String)): G[Option[String]] = f( - self.modify(key, fn)) def set(key: String, value: String): G[Unit] = f(self.set(key, value)) - def update(key: String, fn: String => String): G[Unit] = f(self.update(key, fn)) def unset(key: String) = f(self.unset(key)) + def entries: G[Properties] = f(self.entries) } } @@ -159,21 +125,11 @@ object Prop { def get(key: String): F[Option[String]] = F.delay(Option(System.getProperty(key))) // thread-safe - def modify(key: String, f: String => (String, String)): F[Option[String]] = F.delay { - Option(System.getProperty(key)).map { value => - val (newValue, output) = f(value) - System.setProperty(key, newValue) - output - } - } - def set(key: String, value: String): F[Unit] = F.void(F.delay(System.setProperty(key, value))) - def update(key: String, f: String => String): F[Unit] = F.void( - F.delay(Option(System.getProperty(key)).map(value => System.setProperty(key, f(value)))) - ) - def unset(key: String): F[Unit] = F.void(F.delay(System.clearProperty(key))) + + def entries: F[Properties] = F.delay(System.getProperties()) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala index e5be184a6a..6b5abc5c36 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala @@ -28,44 +28,18 @@ class PropSpec extends BaseSpec { "return none for a non-existent property" in real { Prop[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) } - "getAndSet" in real { - Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - for { - _ <- Prop[IO].set(key, "bar") - getAndSetResult <- Prop[IO].getAndSet(key, "baz") - getResult <- Prop[IO].get(key) - } yield { - getAndSetResult mustEqual Some("bar") - getResult mustEqual Some("baz") - } - } - } - "getAndUpdate" in real { - Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - for { - _ <- Prop[IO].set(key, "bar") - getAndSetResult <- Prop[IO].getAndUpdate(key, v => v + "baz") - getResult <- Prop[IO].get(key) - } yield { - getAndSetResult mustEqual Some("bar") - getResult mustEqual Some("barbaz") - } - } - } "unset" in real { Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => Prop[IO].set(key, "bar") *> Prop[IO] .unset(key) *> Prop[IO].get(key).flatMap(x => IO(x must beNone)) } } - "not modify anything if a value for the key does not exist" in real { - Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - for { - _ <- Prop[IO].get(key).flatMap(x => IO(x must beNone)) - x <- Prop[IO].modify(key, _ => ("new value", "output")) - assertion <- IO(x must beNone) - } yield assertion - } + "retrieve the system properties" in real { + for { + _ <- Prop[IO].set("some property", "the value") + props <- Prop[IO].entries + assertion <- IO(props mustEqual System.getProperties()) + } yield assertion } } } From cbad5e509af8030d8475549ba0da072b668d70e1 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Wed, 10 Jan 2024 19:13:02 +0100 Subject: [PATCH 06/14] use an immutable map --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 58b17cf17c..34525afda5 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -21,7 +21,7 @@ import cats.data.{EitherT, IorT, Kleisli, OptionT, ReaderWriterStateT, StateT, W import cats.effect.kernel.Sync import cats.kernel.Monoid -import java.util.Properties +import scala.jdk.CollectionConverters.PropertiesHasAsScala trait Prop[F[_]] { self => @@ -41,13 +41,13 @@ trait Prop[F[_]] { self => */ def unset(key: String): F[Unit] - def entries: F[Properties] + def entries: F[Map[String, String]] def mapK[G[_]](f: F ~> G): Prop[G] = new Prop[G] { def get(key: String): G[Option[String]] = f(self.get(key)) def set(key: String, value: String): G[Unit] = f(self.set(key, value)) def unset(key: String) = f(self.unset(key)) - def entries: G[Properties] = f(self.entries) + def entries: G[Map[String, String]] = f(self.entries) } } @@ -130,6 +130,6 @@ object Prop { def unset(key: String): F[Unit] = F.void(F.delay(System.clearProperty(key))) - def entries: F[Properties] = F.delay(System.getProperties()) + def entries: F[Map[String, String]] = F.delay(Map.from(System.getProperties().asScala)) } } From 49fa3b2991535f195e8fa1da8e71ce71ff73a4a6 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 27 May 2024 14:49:47 +0000 Subject: [PATCH 07/14] Update headers --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 2 +- tests/shared/src/test/scala/cats/effect/std/PropSpec.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 34525afda5..45f563d586 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Typelevel + * Copyright 2020-2024 Typelevel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala index 6b5abc5c36..c3e1374c39 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Typelevel + * Copyright 2020-2024 Typelevel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 1e2bf0731d718a9da30c7dbc4c4fd0c3fd0864ae Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 27 May 2024 15:06:11 +0000 Subject: [PATCH 08/14] Fix compile on 2.12 --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 45f563d586..c5723084f7 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -21,7 +21,9 @@ import cats.data.{EitherT, IorT, Kleisli, OptionT, ReaderWriterStateT, StateT, W import cats.effect.kernel.Sync import cats.kernel.Monoid -import scala.jdk.CollectionConverters.PropertiesHasAsScala +import org.typelevel.scalaccompat.annotation._ + +import scala.collection.immutable.Map trait Prop[F[_]] { self => @@ -130,6 +132,11 @@ object Prop { def unset(key: String): F[Unit] = F.void(F.delay(System.clearProperty(key))) - def entries: F[Map[String, String]] = F.delay(Map.from(System.getProperties().asScala)) + @nowarn213("cat=deprecation") + @nowarn3("cat=deprecation") + def entries: F[Map[String, String]] = { + import scala.collection.JavaConverters._ + F.delay(Map.empty ++ System.getProperties().asScala) + } } } From 967ca632e6a6f4505b93181c48e1dbbb19a009ad Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 15 Jul 2024 11:15:49 -0700 Subject: [PATCH 09/14] Use `blocking` when setting a property --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index c5723084f7..071aeabbd9 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -128,7 +128,7 @@ object Prop { F.delay(Option(System.getProperty(key))) // thread-safe def set(key: String, value: String): F[Unit] = - F.void(F.delay(System.setProperty(key, value))) + F.void(F.blocking(System.setProperty(key, value))) def unset(key: String): F[Unit] = F.void(F.delay(System.clearProperty(key))) From f570b7ef7635fecfc1dc9ac20f89ec502a1569f5 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 15 Jul 2024 18:48:27 +0000 Subject: [PATCH 10/14] Make `entries` atomic --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 071aeabbd9..31bb9dae6e 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -134,9 +134,13 @@ object Prop { @nowarn213("cat=deprecation") @nowarn3("cat=deprecation") - def entries: F[Map[String, String]] = { - import scala.collection.JavaConverters._ - F.delay(Map.empty ++ System.getProperties().asScala) - } + def entries: F[Map[String, String]] = + F.blocking { + import scala.collection.JavaConverters._ + val props = System.getProperties + val back = new java.util.HashMap[String, String](props.size()) + props.putAll(back) + Map.empty ++ back.asScala + } } } From 923c0d5f521cf212c69b4a813f52554dad8f4013 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Tue, 30 Jul 2024 16:22:51 +0200 Subject: [PATCH 11/14] compare the same types --- tests/shared/src/test/scala/cats/effect/std/PropSpec.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala index c3e1374c39..2a97ce07ec 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala @@ -17,6 +17,8 @@ package cats.effect package std +import scala.jdk.CollectionConverters._ + class PropSpec extends BaseSpec { "Prop" should { @@ -38,7 +40,8 @@ class PropSpec extends BaseSpec { for { _ <- Prop[IO].set("some property", "the value") props <- Prop[IO].entries - assertion <- IO(props mustEqual System.getProperties()) + expected <- IO(Map.empty ++ System.getProperties.asScala) + assertion <- IO(props mustEqual expected) } yield assertion } } From 9436fd64667265d8234c15451ad1f90b043cb388 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 7 Aug 2024 21:11:22 +0000 Subject: [PATCH 12/14] Fix deprecations in test --- std/shared/src/main/scala/cats/effect/std/Prop.scala | 3 +-- tests/shared/src/test/scala/cats/effect/std/PropSpec.scala | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/Prop.scala index 31bb9dae6e..0511af7bc6 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/Prop.scala @@ -138,8 +138,7 @@ object Prop { F.blocking { import scala.collection.JavaConverters._ val props = System.getProperties - val back = new java.util.HashMap[String, String](props.size()) - props.putAll(back) + val back = props.clone().asInstanceOf[java.util.Map[String, String]] Map.empty ++ back.asScala } } diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala index 2a97ce07ec..2e5cea0b1e 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala @@ -17,8 +17,6 @@ package cats.effect package std -import scala.jdk.CollectionConverters._ - class PropSpec extends BaseSpec { "Prop" should { @@ -40,7 +38,10 @@ class PropSpec extends BaseSpec { for { _ <- Prop[IO].set("some property", "the value") props <- Prop[IO].entries - expected <- IO(Map.empty ++ System.getProperties.asScala) + expected <- IO { + import scala.collection.JavaConverters._ + Map.empty ++ System.getProperties.asScala + }: @nowarn213("cat=deprecation") @nowarn3("cat=deprecation") assertion <- IO(props mustEqual expected) } yield assertion } From 4a71b854ead796151d62205a0f65445d0dff08e2 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 8 Aug 2024 05:58:52 +0000 Subject: [PATCH 13/14] `Prop` -> `SystemProperties` --- .../src/main/scala/cats/effect/IO.scala | 4 +- .../{Prop.scala => SystemProperties.scala} | 52 +++++++++---------- ...pSpec.scala => SystemPropertiesSpec.scala} | 19 ++++--- 3 files changed, 39 insertions(+), 36 deletions(-) rename std/shared/src/main/scala/cats/effect/std/{Prop.scala => SystemProperties.scala} (65%) rename tests/shared/src/test/scala/cats/effect/std/{PropSpec.scala => SystemPropertiesSpec.scala} (68%) diff --git a/core/shared/src/main/scala/cats/effect/IO.scala b/core/shared/src/main/scala/cats/effect/IO.scala index 2b85fd6b55..50d14c7a44 100644 --- a/core/shared/src/main/scala/cats/effect/IO.scala +++ b/core/shared/src/main/scala/cats/effect/IO.scala @@ -40,7 +40,7 @@ import cats.data.Ior import cats.effect.instances.spawn import cats.effect.kernel.CancelScope import cats.effect.kernel.GenTemporal.handleDuration -import cats.effect.std.{Backpressure, Console, Env, Prop, Supervisor, UUIDGen} +import cats.effect.std.{Backpressure, Console, Env, Supervisor, SystemProperties, UUIDGen} import cats.effect.tracing.{Tracing, TracingEvent} import cats.effect.unsafe.IORuntime import cats.syntax._ @@ -2108,7 +2108,7 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara implicit val envForIO: Env[IO] = Env.make - implicit val propForIO: Prop[IO] = Prop.make + implicit val systemPropertiesForIO: SystemProperties[IO] = SystemProperties.make // This is cached as a val to save allocations, but it uses ops from the Async // instance which is also cached as a val, and therefore needs to appear diff --git a/std/shared/src/main/scala/cats/effect/std/Prop.scala b/std/shared/src/main/scala/cats/effect/std/SystemProperties.scala similarity index 65% rename from std/shared/src/main/scala/cats/effect/std/Prop.scala rename to std/shared/src/main/scala/cats/effect/std/SystemProperties.scala index 0511af7bc6..4fd35d62d7 100644 --- a/std/shared/src/main/scala/cats/effect/std/Prop.scala +++ b/std/shared/src/main/scala/cats/effect/std/SystemProperties.scala @@ -25,7 +25,7 @@ import org.typelevel.scalaccompat.annotation._ import scala.collection.immutable.Map -trait Prop[F[_]] { self => +trait SystemProperties[F[_]] { self => /** * Retrieves the value for the specified key. @@ -45,7 +45,7 @@ trait Prop[F[_]] { self => def entries: F[Map[String, String]] - def mapK[G[_]](f: F ~> G): Prop[G] = new Prop[G] { + def mapK[G[_]](f: F ~> G): SystemProperties[G] = new SystemProperties[G] { def get(key: String): G[Option[String]] = f(self.get(key)) def set(key: String, value: String): G[Unit] = f(self.set(key, value)) def unset(key: String) = f(self.unset(key)) @@ -53,76 +53,76 @@ trait Prop[F[_]] { self => } } -object Prop { +object SystemProperties { /** - * Summoner method for `Prop` instances. + * Summoner method for `SystemProperties` instances. */ - def apply[F[_]](implicit ev: Prop[F]): ev.type = ev + def apply[F[_]](implicit ev: SystemProperties[F]): ev.type = ev /** - * Constructs a `Prop` instance for `F` data types that are [[cats.effect.kernel.Sync]]. + * Constructs a `SystemProperties` instance for `F` data types that are [[cats.effect.kernel.Sync]]. */ - def make[F[_]](implicit F: Sync[F]): Prop[F] = new SyncProp[F] + def make[F[_]](implicit F: Sync[F]): SystemProperties[F] = new SyncSystemProperties[F] /** * [[Prop]] instance built for `cats.data.EitherT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsEitherTProp[F[_]: Prop: Functor, L]: Prop[EitherT[F, L, *]] = - Prop[F].mapK(EitherT.liftK) + implicit def catsEitherTSystemProperties[F[_]: SystemProperties: Functor, L]: SystemProperties[EitherT[F, L, *]] = + SystemProperties[F].mapK(EitherT.liftK) /** * [[Prop]] instance built for `cats.data.Kleisli` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsKleisliProp[F[_]: Prop, R]: Prop[Kleisli[F, R, *]] = - Prop[F].mapK(Kleisli.liftK) + implicit def catsKleisliSystemProperties[F[_]: SystemProperties, R]: SystemProperties[Kleisli[F, R, *]] = + SystemProperties[F].mapK(Kleisli.liftK) /** * [[Prop]] instance built for `cats.data.OptionT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsOptionTProp[F[_]: Prop: Functor]: Prop[OptionT[F, *]] = - Prop[F].mapK(OptionT.liftK) + implicit def catsOptionTSystemProperties[F[_]: SystemProperties: Functor]: SystemProperties[OptionT[F, *]] = + SystemProperties[F].mapK(OptionT.liftK) /** * [[Prop]] instance built for `cats.data.StateT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsStateTProp[F[_]: Prop: Applicative, S]: Prop[StateT[F, S, *]] = - Prop[F].mapK(StateT.liftK) + implicit def catsStateTSystemProperties[F[_]: SystemProperties: Applicative, S]: SystemProperties[StateT[F, S, *]] = + SystemProperties[F].mapK(StateT.liftK) /** * [[Prop]] instance built for `cats.data.WriterT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsWriterTProp[ - F[_]: Prop: Applicative, + implicit def catsWriterTSystemProperties[ + F[_]: SystemProperties: Applicative, L: Monoid - ]: Prop[WriterT[F, L, *]] = - Prop[F].mapK(WriterT.liftK) + ]: SystemProperties[WriterT[F, L, *]] = + SystemProperties[F].mapK(WriterT.liftK) /** * [[Prop]] instance built for `cats.data.IorT` values initialized with any `F` data type that * also implements `Prop`. */ - implicit def catsIorTProp[F[_]: Prop: Functor, L]: Prop[IorT[F, L, *]] = - Prop[F].mapK(IorT.liftK) + implicit def catsIorTSystemProperties[F[_]: SystemProperties: Functor, L]: SystemProperties[IorT[F, L, *]] = + SystemProperties[F].mapK(IorT.liftK) /** * [[Prop]] instance built for `cats.data.ReaderWriterStateT` values initialized with any `F` * data type that also implements `Prop`. */ - implicit def catsReaderWriterStateTProp[ - F[_]: Prop: Applicative, + implicit def catsReaderWriterStateTSystemProperties[ + F[_]: SystemProperties: Applicative, E, L: Monoid, S - ]: Prop[ReaderWriterStateT[F, E, L, S, *]] = - Prop[F].mapK(ReaderWriterStateT.liftK) + ]: SystemProperties[ReaderWriterStateT[F, E, L, S, *]] = + SystemProperties[F].mapK(ReaderWriterStateT.liftK) - private[std] final class SyncProp[F[_]](implicit F: Sync[F]) extends Prop[F] { + private[std] final class SyncSystemProperties[F[_]](implicit F: Sync[F]) extends SystemProperties[F] { def get(key: String): F[Option[String]] = F.delay(Option(System.getProperty(key))) // thread-safe diff --git a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala b/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSpec.scala similarity index 68% rename from tests/shared/src/test/scala/cats/effect/std/PropSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSpec.scala index 2e5cea0b1e..b0bd9ee4ef 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSpec.scala @@ -17,27 +17,30 @@ package cats.effect package std -class PropSpec extends BaseSpec { +import org.typelevel.scalaccompat.annotation._ - "Prop" should { +class SystemPropertiesSpec extends BaseSpec { + + "SystemProperties" should { "retrieve a property just set" in real { Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - Prop[IO].set(key, "bar") *> Prop[IO].get(key).flatMap(x => IO(x mustEqual Some("bar"))) + SystemProperties[IO].set(key, "bar") *> + SystemProperties[IO].get(key).flatMap(x => IO(x mustEqual Some("bar"))) } } "return none for a non-existent property" in real { - Prop[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) + SystemProperties[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) } "unset" in real { Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - Prop[IO].set(key, "bar") *> Prop[IO] - .unset(key) *> Prop[IO].get(key).flatMap(x => IO(x must beNone)) + SystemProperties[IO].set(key, "bar") *> SystemProperties[IO].unset(key) *> + SystemProperties[IO].get(key).flatMap(x => IO(x must beNone)) } } "retrieve the system properties" in real { for { - _ <- Prop[IO].set("some property", "the value") - props <- Prop[IO].entries + _ <- SystemProperties[IO].set("some property", "the value") + props <- SystemProperties[IO].entries expected <- IO { import scala.collection.JavaConverters._ Map.empty ++ System.getProperties.asScala From 638a1fbe44862da9e1275cf16281dc21f9251f81 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 8 Aug 2024 06:05:57 +0000 Subject: [PATCH 14/14] Formatting --- .../cats/effect/std/SystemProperties.scala | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/std/shared/src/main/scala/cats/effect/std/SystemProperties.scala b/std/shared/src/main/scala/cats/effect/std/SystemProperties.scala index 4fd35d62d7..33325d5713 100644 --- a/std/shared/src/main/scala/cats/effect/std/SystemProperties.scala +++ b/std/shared/src/main/scala/cats/effect/std/SystemProperties.scala @@ -61,7 +61,8 @@ object SystemProperties { def apply[F[_]](implicit ev: SystemProperties[F]): ev.type = ev /** - * Constructs a `SystemProperties` instance for `F` data types that are [[cats.effect.kernel.Sync]]. + * Constructs a `SystemProperties` instance for `F` data types that are + * [[cats.effect.kernel.Sync]]. */ def make[F[_]](implicit F: Sync[F]): SystemProperties[F] = new SyncSystemProperties[F] @@ -69,28 +70,32 @@ object SystemProperties { * [[Prop]] instance built for `cats.data.EitherT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsEitherTSystemProperties[F[_]: SystemProperties: Functor, L]: SystemProperties[EitherT[F, L, *]] = + implicit def catsEitherTSystemProperties[F[_]: SystemProperties: Functor, L] + : SystemProperties[EitherT[F, L, *]] = SystemProperties[F].mapK(EitherT.liftK) /** * [[Prop]] instance built for `cats.data.Kleisli` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsKleisliSystemProperties[F[_]: SystemProperties, R]: SystemProperties[Kleisli[F, R, *]] = + implicit def catsKleisliSystemProperties[F[_]: SystemProperties, R] + : SystemProperties[Kleisli[F, R, *]] = SystemProperties[F].mapK(Kleisli.liftK) /** * [[Prop]] instance built for `cats.data.OptionT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsOptionTSystemProperties[F[_]: SystemProperties: Functor]: SystemProperties[OptionT[F, *]] = + implicit def catsOptionTSystemProperties[F[_]: SystemProperties: Functor] + : SystemProperties[OptionT[F, *]] = SystemProperties[F].mapK(OptionT.liftK) /** * [[Prop]] instance built for `cats.data.StateT` values initialized with any `F` data type * that also implements `Prop`. */ - implicit def catsStateTSystemProperties[F[_]: SystemProperties: Applicative, S]: SystemProperties[StateT[F, S, *]] = + implicit def catsStateTSystemProperties[F[_]: SystemProperties: Applicative, S] + : SystemProperties[StateT[F, S, *]] = SystemProperties[F].mapK(StateT.liftK) /** @@ -107,7 +112,8 @@ object SystemProperties { * [[Prop]] instance built for `cats.data.IorT` values initialized with any `F` data type that * also implements `Prop`. */ - implicit def catsIorTSystemProperties[F[_]: SystemProperties: Functor, L]: SystemProperties[IorT[F, L, *]] = + implicit def catsIorTSystemProperties[F[_]: SystemProperties: Functor, L] + : SystemProperties[IorT[F, L, *]] = SystemProperties[F].mapK(IorT.liftK) /** @@ -122,7 +128,8 @@ object SystemProperties { ]: SystemProperties[ReaderWriterStateT[F, E, L, S, *]] = SystemProperties[F].mapK(ReaderWriterStateT.liftK) - private[std] final class SyncSystemProperties[F[_]](implicit F: Sync[F]) extends SystemProperties[F] { + private[std] final class SyncSystemProperties[F[_]](implicit F: Sync[F]) + extends SystemProperties[F] { def get(key: String): F[Option[String]] = F.delay(Option(System.getProperty(key))) // thread-safe