There is an example of a simple API that uses ZIO effect to return None or Option[String]. I use ZIO Schedule to run the effect as long the None is returned, but limited to a certain number of times. The example is based on the code from ZIO usecases_scheduling:
import zio._
import zio.random._
import zio.duration._
import zio.console.{Console, putStrLn}
import zio.Schedule
import scala.util.{Random => ScalaUtilRandom}
object RecordAPI {
def randomId(length: Int): String =
LazyList.continually(ScalaUtilRandom.nextPrintableChar).filter(_.isLetterOrDigit).take(length).mkString
def getRecordId: Task[Option[String]] = Task.effect(
if (ScalaUtilRandom.nextInt(10) >= 7) Some(randomId(16)) else None
)
}
object ScheduleUtil {
def schedule[A]: Schedule[Random, Option[String], Option[String]] =
(Schedule.exponential(10.milliseconds) && Schedule.recurs(10)) *> Schedule.recurWhile(_.isEmpty)
}
object RandomScheduler extends scala.App {
implicit val rt: Runtime[zio.ZEnv] = Runtime.default
rt.unsafeRun {
RecordAPI.getRecordId
.repeat(ScheduleUtil.schedule)
.foldM(
ex => putStrLn(s"failed with ${ex.getMessage}"),
success => putStrLn(s"Succeeded with $success")
)
}
}
This effect below has the type ZIO[Random with clock.Clock, Throwable, Option[String]]:
RecordAPI.getRecordId.repeat(ScheduleUtil.schedule)
I would like to remove the ScheduleUtil.schedule dependency on Random by providing the Random env and to receive the effect ZIO[Any with clock.Clock, Throwable, Option[String]]:
RecordAPI.getRecordId.repeat(ScheduleUtil.schedule.provide(Random))
but I get compilation error:
[error] found : zio.random.Random.type
[error] required: zio.random.Random
[error] (which expands to) zio.Has[zio.random.Random.Service]
[error] .repeat(ScheduleUtil.schedule.provide(Random))
[error] ^
[error] one error found
What parameter should be provided to the .provide method?
Error message talks you that you tries to pass to function provide Random.type
in the line:
RecordAPI.getRecordId.repeat(ScheduleUtil.schedule.provide(Random))
Random is passed as type but provide expects instance of Random. So you can make your code compilable just replacing Random type to some it's instance:
val hasRandomService: Random = Has.apply(Random.Service.live)
val randomIdZIO: ZIO[Random, Throwable, Option[String]] =
RecordAPI.getRecordId.repeat(ScheduleUtil.schedule.provide(hasRandomService))
but if you want to get rid of ScheduleUtil.schedule maybe it's better to use Schedule.fromFunction function:
val randomIdZIOFromFunction: ZIO[Random, Throwable, Option[String]] =
RecordAPI.getRecordId.repeat(
Schedule.fromFunction(_ => if (ScalaUtilRandom.nextInt(10) >= 7) Some(randomId(16)) else None)
)
Related
I'm trying to implement a query that returns the extracted information inside a fs2.Stream. I defined the Jobs algebra:
trait Jobs[F[_]] {
def all(): fs2.Stream[F, Job]
}
Then, I implemented an interpreter for the algebra:
final class LiveJobs[F[_]: MonadCancelThrow](postgres: Resource[F, Transactor[F]]) extends Jobs[F] {
override def all(): fs2.Stream[F, Job] = for {
jobs <- postgres.use { xa =>
sql"SELECT * FROM jobs".query[Job].stream.transact(xa)
}
} yield jobs
}
However, the compiler yells because the types are not aligned:
type mismatch;
[error] found : fs2.Stream[[_]F[_],Job]
[error] required: F[?]
[error] sql"SELECT * FROM jobs".query[Job].stream.transact(xa)
[error] ^
[error] one error found
The Resource.use method needs a function that produces an F[*], not an fs2.Stream[F, Job]. I cannot find anything that lets me convert between the two types or a different way to use the postgres resource.
The following is probably the design you want to follow:
trait Jobs[F[_]] {
def all: fs2.Stream[F, Job] =
}
object Jobs {
// I am not exactly sure which typeclass you require here, so i will use Async
def live[F[_]](implicit ev: Async[F]): Resource[F, Jobs[F]] = {
val transactor: Resource[F, Transactor[F]] = ... // Whatever you already have here.
transactor.map(xa => new LiveJobs(xa))
}
}
private[pckg] final class LiveJobs[F[_]](xa: Transactor[F])(implicit ev: MonadCancelThrow[F]) extends Jobs[F] {
override final val all: fs2.Stream[F, Job] =
sql"SELECT * FROM jobs".query[Job].stream.transact(xa)
}
Also, my personal advice, stick to concrete IO while learning; and maybe even after.
The whole F[_] thing will just cause more trouble than worth originally.
I want to test my method which returns an Either. This is how i do this:
#Test def `Test empty name is not valid`: Unit = {
val book = createBook()
val result = insertEntryToBook(book, "", "12345")
result match {
case Left(InvalidNameFormat) => true
case _ => fail()
}
}
Should i do some assert() call instead of fail() to make the test's fail message more explicit (e.g. for assertion side-to-side view)?
There is no reason to pattern match here. You can just assert the result equals the expected value wrapped in a Left().
assertEquals(Left(InvalidNameFormat), result)
In case your test fails, you get a precise error message.
ScalaTest can be used with JUnit, so consider mixing in EitherValues which should give more informative messages
import org.junit.Test
import org.scalatest.{EitherValues, Matchers}
class ExampleSuite extends Matchers with EitherValues {
#Test def matchEithers(): Unit = {
val result: Either[String, Int] = Right(42)
result.left.value should be ("Boom")
}
}
which gives
org.scalatest.exceptions.TestFailedException: The Either on which left.value was invoked was not defined as a Left.
at org.scalatest.EitherValues$LeftValuable.value(EitherValues.scala:124)
at example.ExampleSuite.matchEithers(ExampleSuite.scala:9)
...
I've got some code that returns an IO but I need a Effect in http4s.
import cats.effect.{Effect, IO}
class Service[F[_]: Effect] extends Http4sDsl[F] {
val service: HttpService[F] = {
HttpService[F] {
case GET -> Root =>
val data: IO[String] = getData()
data.map(d => Ok(d))
}
}
}
gives
[error] found : cats.effect.IO[F[org.http4s.Response[F]]]
[error] required: F[org.http4s.Response[F]]
[error] data.map(d => Ok(d))
[error] ^
One way we can get around using a concrete IO[A] is using LiftIO[F]:
class Service[F[_]: Effect] extends Http4sDsl[F] {
val service: HttpService[F] = {
HttpService[F] {
case GET -> Root =>
getData().liftIO[F].flatMap(Ok(_))
}
}
}
LiftIO lifts will lift: IO[A] => F[A], but this yields us F[F[Response[F]. In order to get things to compile, we'll flatten on F since it has a Monad (or FlatMap) instance in cats due to our Effect context bounds requirement.
If we want more detail, this is the -Xprint:typer result:
cats.implicits.catsSyntaxFlatten[F, org.http4s.Response[F]](
cats.effect.LiftIO.apply[F](Service.this.evidence$1)
.liftIO[F[org.http4s.Response[F]]](
data.map[F[org.http4s.Response[F]]](
((d: String) => Service.this.http4sOkSyntax(Service.this.Ok)
.apply[String](d)(Service.this.evidence$1,
Service.this.stringEncoder[F](
Service.this.evidence$1, Service.this.stringEncoder$default$2[F]))))))(Service.this.evidence$1).flatten(Service.this.evidence$1)
And at the end of the world when you want to give a concrete effect, for example Service[IO], we get:
val serv: Service[cats.effect.IO] =
new Service[cats.effect.IO]()(effect.this.IO.ioConcurrentEffect)
Where ioConcurrentEffect is the Effect[IO] instance.
It seems that all the good stuff is defined at org.http4s.syntax.AllSyntax trait.
Is it possible to pattern match on a lazy val, declared as a Try, like this?
lazy val kafkaProducer: Try[producer.KafkaProducer[Array[Byte], String]] = Try(kafkaProducerSettings.createKafkaProducer())
...
kafkaProducer.get match {
case Success(_) => Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, kafkaProducer.get))
case Failure(x) => Future.failed(x)
}
I'm getting this error:
constructor cannot be instantiated to expected type;
[error] found : akka.actor.Status.Success
[error] required: org.apache.kafka.clients.producer.KafkaProducer[Array[Byte],String]
[error] case Success(_) => Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, kafkaProducer.get))
Note, this alternative code works, but I'm not sure it's the "Scala way":
lazy val kafkaProducer: producer.KafkaProducer[Array[Byte], String] = kafkaProducerSettings.createKafkaProducer()
...
val tryAccessLazyKafkaProducer = Try(kafkaProducer)
if (tryAccessLazyKafkaProducer.isSuccess) {
Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, kafkaProducer))
} else {
Future.failed(tryAccessLazyKafkaProducer.failed.get)
}
It's definitely possible, you just have the wrong Success type imported:
found : akka.actor.Status.Success
You need scala.util.Success instead
One thing you mustn't do is call Try.get, which will explode if the returned type is a Failure. Instead, do:
import scala.util.Success
import scala.util.Failure
kafkaProducer match {
case Success(producer) => Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, producer))
case failure: Failure => failure
}
lazy is just a language construct which makes sure the value is only ever evaluated once. The underlying type, whether lazy or not, is still a Try which you can do what you do with it.
Code below is a simplified version of the real code. We "inherited" the domain model case object FutTest and case class FutTest, which we can't modify. The actual domain models are served from a Database, so I believe the Future approach is valid, but it causes problems which I don't understand.
import org.scalatest.FunSpec
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
case object FutTest {
def create(sz: Int) = { FutTest(sz) }
}
case class FutTest(size: Int)
class FutureTest extends FunSpec {
def one(v: Int): Future[FutTest] = {
Future { FutTest.create(v) }
}
def two(t: FutTest) = {
Future { FutTest.create(t.size) }
}
def compileError1: Future[FutTest] = {
one(10).map(f => two(f))
}
def compileError2: Future[FutTest] = {
for { o <- one(10) } yield (two(o))
}
}
The error messages:
[INFO] Using incremental compilation
[INFO] Compiling 7 Scala sources and 5 .. target/test-classes...
[ERROR] domain.FutureTest.scala:25: type mismatch;
found : scala.concurrent.Future[domain.FutTest]
required: domain.FutTest
[ERROR] one(10).map(f => two(f))
[ERROR] ^
[ERROR] domain/FutureTest.scala:29: type mismatch;
found : scala.concurrent.Future[domain.FutTest]
required: domain.FutTest
[ERROR] for { o <- one(10) } yield (two(o))
I tried the above code with plain Int instead of FutTest and all is fine. Why is the compiler complaining and how can we solve this without touching the existing domain.
flatMap is what you want.
one(10).flatMap(f => two(f))
or
one(10).flatMap(two)
Using for comprehension,
for { o <- one(10); t <- two(o) } yield t
One() returns a Future and two() also returns a Future so you need to flatMap instead of map. When you map to two(), your result is Future[Future[FutTest]] and needs to be flattened.
Doing
one(10).flatMap(f => two(f))
should do the trick.