Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
    Richard
    @Executioner1939
    ignore the fact that it is returning a BIO[Throwable, Option[A]] I know this is monix.bio.Task, it will eventually be a custom error type, just porting code at the moment
    But from requires a TaskLike for monix.eval.Task
    Piotr Gawryś
    @Avasil
    @Executioner1939 Cool, I have some ideas, I will ping you when I create issues or you can take over something that didn't have any update in a long time
    Piotr Gawryś
    @Avasil

    TaskLike instance should be available if there is Scheduler in scope.

    val bio: BIO[Throwable, Int] = BIO(10)
    // monix.eval
    val task: Task[Int] = Task.deferAction(implicit s => Task.from(bio))

    I'm considering a private type class in monix-execution for types which already have access to Scheduler so it is easier to convert.
    For Observable there could be extra module with BIO-native methods to use them without conversions but it's very low priority for me atm

    Richard
    @Executioner1939
    @Avasil thanks for the help :-), as always greatly appreciated
    Romain DEP.
    @rom1dep

    Hello there,
    I'm trying to understand what BIO is about :)

    So far, I get that I can use IO to annotate the error type on top of the result type:

      case class SomethingBusinessException() extends Exception
      def doSomethingWithBIO: IO[SomethingBusinessException, Int] = {???}

    but then from the "Error Channels" examples at https://bio.monix.io/docs/error-handling , I see that the implementation is not itself an IO, so, following the same pattern that uses IO.now, I could write something silly, like:

      def doSomethingWithBIO: IO[SomethingBusinessException, Int] = {
        Random.nextBoolean() match {
          case true => IO.raiseError(SomethingBusinessException()) ← gets caught in redeem
          case false => IO.now(1/0) // ← Fake unforeseen exception
        }
      }
    
    IO("OK").flatMap(_ => doSomethingWithBIO).redeem(_ => "KO", _ => "OK")

    it compiles, is type safe, but blows up (=throws an ArithmeticException) at runtime.
    So, how should I use BIO in the general case (that is all cases) when not all exceptions are foreseen?

    I guess doing that would defeat the purpose:

      def doSomethingWithBIO: IO[SomethingBusinessException, Either[Throwable, Int]] = {
        Random.nextBoolean() match {
          case true => IO.raiseError(SomethingBusinessException()) ← gets caught in redeem
          case false => Try{1/0}.toEither ← would get caught in redeem@Right
        }
      }

    but I imagine so would making it crash until all the possible "failure modes" are figured-out and enriched into SomethingBusinessException

    Piotr Gawryś
    @Avasil

    The idea is to split "foreseen" and "unforeseen" exceptions based on whether you can sensibly recover from them, or not.
    When you interact with any impure code that could throw exceptions, you should wrap it with IO.apply that returns IO[Throwable, A] and catches all of them in a "typed" error channel.

    You could then use any error handling function to map it to your domain errors. If there could be "unknown" errors that you have no idea what to do with, you can hide them as unexpected errors and they will skip most error handlers. Or just log and ignore them. It's considered okay if they blow up at runtime because this channel is meant for errors that we can't do anything about anyway.

    e.g.

      def unsafeFunction: Boolean = {
        Random.nextBoolean() match {
          case true => true
          case false => 1/0
        }
      }
    
    def unsafeFunctionBIO: IO[SomethingBusinessException, Boolean] = {
       IO(unsafeFunction).redeemWith({
          // or just handle right away
          case ThrowableIKnowHowToHandle => IO.raiseError(SomethingBusinessException())
          // terminate or just ignore with `IO.now`
          case other => IO.terminate(other)
          }, x => x
        )

    At the edges, you could access them with redeemCause with a generic error log, InternalServerError etc.
    I'm not sure if it answers your questions but I'm happy to elaborate. I will be doing another pass at this doc section soon to improve examples so discussions like that are very helpful.

    Romain DEP.
    @rom1dep

    Hey @Avasil , thanks for your reply, that does clarify some :)

    I can better picture how all the unsafe+"unknowingly unsafe" code can be wrapped in a Task with redeemWith as the "constructor" for the typed IO,
    Now I've got to see how to chain those IO[E, A] into meaningful programs.
    By the way, this is more of a "style" question, but is there a reason not to have a IO.then(_) that would alias to IO.flatMap? I know that flatMap has a special place in scala due to the sugaring in for comprehensions, but that style doesn't seem en vogue with Bio anyway (from what I've seen of the documentation), and flatMap sounds like cognitive load for newcomers or simply people use to other languages.

    Then about the doc, I think it's great :) (structure is good, examples are plenty),
    The format I like the best for this kind of things are step-by-step projects (rather than zoos of heterogeneous features) because one gets to build an appreciation for the kind of problems each feature of the lib addresses and how they all play together into something meaningful (and relatable to e.g. the more imperative alternative everyone is used to).
    Perhaps a tutorial for building a tiny program that authenticates to a REST API then to run some queries in parallel would be great at demonstrating the essence of IO and async/parallel capabilities of monix :smiley:

    Piotr Gawryś
    @Avasil
    Yeah, I would love to have a sample app with a tutorial and it's on my list after I finish proper docs
    Piotr Gawryś
    @Avasil

    By the way, this is more of a "style" question, but is there a reason not to have a IO.then(_) that would alias to IO.flatMap?

    I'm not sure if there were attempts in other libraries but I feel like flatMap is so common in Scala that it's better to learn it eventually. Documentation would have to choose either flatMap or then. then could potentially help some of the beginners but most of the audience would be confused because it's not consistent with the ecosystem. I think it would only work if everyone agrees to a new name. And there are extra considerations like streams where flatMap produces a new stream for each element so it's not quite "then"

    has a special place in scala due to the sugaring in for comprehensions, but that style doesn't seem en vogue with Bio anyway (from what I've seen of the documentation)

    If I don't use for in the docs it's because either example is too short, or I want to emphasize the function I use. :D In practice, for comprehension, is very common regardless if you use IO[E, A] or IO[A]

    Romain DEP.
    @rom1dep
    alright, thanks @Avasil for the pointers :smiley:
    Rohan Sircar
    @rohan-sircar
    Hi @Avasil, I have been meaning to contribute to open source projects for a while. Would you happen to have something I could do?
    I am fairly familiar with Scala and FP, and you could see a project I did with Monix here
    Piotr Gawryś
    @Avasil

    Hi @rohan-sircar - that's awesome!

    There are few low-hanging fruits in monix/monix : monix/monix#1183 and monix/monix#1243 (+ the same in monix-bio) if you'd like to try something simple to get used to contributing.

    I'll try to find more tasks in few days
    Is there anything, in particular, that would be interesting to you?

    Romain DEP.
    @rom1dep
    rohan-sircar: that's cool! I've been meaning to use monix-bio for a scalafx app built on top of babbler (a Java XMPP library), I haven't yet quite grasped how to architecture properly something around monix (being it's own thing/effects system), javafx (having its own thread) and babbler (having its own thread for managing the connection and callbacks)
    Rohan Sircar
    @rohan-sircar

    Thanks @Avasil, I looked at those issues and I think I might be able to do those.

    Is there anything, in particular, that would be interesting to you?

    There are things I'm interested in, but at this point I'm more concerned with finding things to do within my skill level and getting comfortable with contributing.

    Rohan Sircar
    @rohan-sircar

    @rom1dep that's the problem I was trying to solve as well. What I did was I just wrapped javafx's Platform.runLater in an ExecutionContext so I can run all operations on it as long as they're non blocking . So no more nested Platform.runLater's. That makes it very similar to how it is in javascript (single threaded everything).

    I then used deferAction in button callbacks to avoid explicitly providing a scheduler at those places. In addition to that I also made a class containing schedulers for cpu bound computations and blocking io that I can pass around to override the fx scheduler if needed and avoid blocking the UI. That's how I did thread pool management.

    Romain DEP.
    @rom1dep
    image.png
    @rohan-sircar what you describe sounds oddly close to what I naively did :)
    image.png
    (but yeah, that last one shouldn't go into a UIO!)
    Bogdan Roman
    @bogdanromanx
    hi, we’ve recently started using monix-bio and it’s a delight. Thanks for creating it! ;)
    as one could expect, we quickly ran into the problem of using cats-effect concurrency primitives, like Ref and Semaphore with typed errors because of the lack of TC instances
    using Task does the job, but in for comprehensions you have to use attempt and then reconstruct the IO from the Either… which is a bit annoying
    so I’ve created something like this:
    trait IOSemaphore {
      def available: IO[Nothing, Long]
      def count: IO[Nothing, Long]
      def acquireN(n: Long): IO[Nothing, Unit]
      def acquire: IO[Nothing, Unit] = acquireN(1)
      def tryAcquireN(n: Long): IO[Nothing, Boolean]
      def tryAcquire: IO[Nothing, Boolean] = tryAcquireN(1)
      def releaseN(n: Long): IO[Nothing, Unit]
      def release: IO[Nothing, Unit] = releaseN(1)
      def withPermit[E, A](t: IO[E, A]): IO[E, A]
    
    }
    
    object IOSemaphore {
    
      final def fromTask(semaphore: Semaphore[Task]): IOSemaphore =
        new IOSemaphore {
          override def available: IO[Nothing, Long]               = semaphore.available.hideErrors
          override def count: IO[Nothing, Long]                   = semaphore.count.hideErrors
          override def acquireN(n: Long): IO[Nothing, Unit]       = semaphore.acquireN(n).hideErrors
          override def tryAcquireN(n: Long): IO[Nothing, Boolean] = semaphore.tryAcquireN(n).hideErrors
          override def releaseN(n: Long): IO[Nothing, Unit]       = semaphore.releaseN(n).hideErrors
          override def withPermit[E, A](t: IO[E, A]): IO[E, A]    =
            semaphore.withPermit(t.attempt).hideErrors.flatMap(IO.fromEither)
        }
    }
    is this a reasonable approach to workaround the current limitation?
    Piotr Gawryś
    @Avasil

    Hi, glad to see you like it!

    I think that's okay if you don't mind the boilerplate.
    I was thinking about providing those in the library but I haven't made up my mind yet.
    There's also an option of creating a hacky TC instance of UIO where all errors are treated as unexpected errors.
    Thankfully, the problem will most likely disappear in CE3

    Bogdan Roman
    @bogdanromanx
    great, thanks for the confirmation; it’s indeed a bit of boilerplate, but less than having to deal with this everywhere

    the problem will most likely disappear in CE3

    have they settled on a solution that avoids EitherT?

    Piotr Gawryś
    @Avasil
    CE3 is not going to provide any direct bifunctor support in type classes but creating Ref, Semaphore etc. won't require Sync so they should be usable with IO[E, A] out of the box
    Bogdan Roman
    @bogdanromanx
    ah, at least that :)
    thanks for the info! ;)
    Bruno Fernandes
    @bfdes

    Hello :wave: ,
    We are looking to add monix-bio support to the weaver test framework, in the same way as we have for Monix: disneystreaming/weaver-test#63

    To that end, I wondered whether there are documented ways to summon Cats Effect instances like Timer, ContextShift, ConcurrentEffectfor monix-bio's Task.

    Piotr Gawryś
    @Avasil

    @bfdes
    Timer and ContextShift should be available for "free" (e.g. https://github.com/monix/monix-bio/blob/master/core/shared/src/main/scala/monix/bio/IO.scala#L5235 ) and ConcurrentEffect if there is implicit Scheduler in scope (IO.catsEffect(s) could work manually) without doing anything. Did you run into any problems?

      implicit def timer: Timer[Task]     = Task.timer(scheduler)
      implicit def cs: ContextShift[Task] = Task.contextShift(scheduler)

    is redundant unless this is a different scheduler than the one you pass when running the task

    Bruno Fernandes
    @bfdes
    @Avasil thanks that worked as
    implicit def timer: Timer[Task]     = IO.timer(scheduler)
    implicit def cs: ContextShift[Task] = IO.contextShift(scheduler)
    Olivier Mélois
    @Baccata

    @Avasil :wave: , in case you don't see the notification : we could use a little bit of guidance there :

    https://github.com/disneystreaming/weaver-test/pull/83/files#r491327459

    Piotr Gawryś
    @Avasil
    I will take a look in a moment:)
    Olivier Mélois
    @Baccata
    weaver-test now has support for monix-bio :)
    Piotr Gawryś
    @Avasil
    Fantastic, I appreciate it! 🎉
    GKhotyan
    @GKhotyan

    Hi! I was trying to make this issue monix/monix-bio#159
    If I understand correctly, I should use method whenA from cats.Monad and write like this:
    def whenA(cond: Boolean)(action: => IO[E, Unit]): IO[E, Unit] =
    MonadIO[E, *].whenA(cond)(action)

    But I have problem with parameter catsMonad. I'm trying to use catsParallelAny.monad here, but facing with type mismatch. Should I find another way to define catsMonad? Or may be I chose wrong way at the beginning?

    Piotr Gawryś
    @Avasil

    Hello @GKhotyan , thank you for the interest in contributing!
    It seems like the signature needs to be final def whenA[E1 >: E](cond: Boolean)(action: => IO[E1, Unit]): IO[E1, Unit] for Applicative[IO[E1, *]].whenA to work.

    On the other hand, I would prefer to reimplement it, the implementation is very simple

    GKhotyan
    @GKhotyan
    Ok, thank you. I’ll try to write implementation of this method
    GKhotyan
    @GKhotyan
    If i’m not mistaken, correct code should be: final def whenA[E1 >: E](cond: Boolean): IO[E1, Unit] = { if (cond) void else unit }
    Piotr Gawryś
    @Avasil

    Ahh, I was wrong with the signature.
    For some reason I put it as an IO method instead of companion object but if we also have a variant on IO then it wouldn't take action at all.
    On companion object it's simply def when[E](cond: Boolean)(action :=> IO[E, Unit]): IO[E, Unit] regardless of Applicative usage.

    BTW, it seems like someone else has already contributed it in the meantime but I have few low hanging fruits in monix/monix if you'd like to look after something else.

    If you'd like to contribute to monix-bio in particular, there are some methods that would be nice to have in the API, like ZIO's absorb:

    final def absorb(implicit ev: E <:< Throwable): Task[A]
    
    final def absorbWith(f: E => Throwable): Task[A]

    that is equivalent of redeemCauseWith(c => IO.raiseError(c.toThrowable), x => IO.now(x))

    Not sure about the name, if there are no better ideas, we can just settle on absorb.

    There are some other methods that I wanted to include in both Monix and Monix BIO, I will create issues for them tomorrow. Feel free to ping me if I don't do that :sweat_smile:

    GKhotyan
    @GKhotyan
    Ok, I’ll try to handle this and be first this time :)
    Piotr Gawryś
    @Avasil
    In general, it's best to post on the issue that you're working on it to prevent such situations :D
    Piotr Gawryś
    @Avasil
    @GKhotyan I've created few more issues! I also have some less trivial ideas in my head if you're interested.
    GKhotyan
    @GKhotyan
    Thank you! I’ll be glad to take something more difficult after low hanging fruit :)
    GKhotyan
    @GKhotyan
    Trying to make IO.flatTap. That’s what I have:
    /** Creates a new Task by applying a monadic function to the successful result
    • of the source Task, and discard the result.
      */
      final def flatTap[E1 >: E, B](f: A => IO[E1, B]): IO[E1, A] = { this.flatMap { a => f(a).map(_ => a) } }
      but comparing to cats flatTap where F could be List or Option, here F is always IO, so I’m confused. May be this code is not what required?
    Piotr Gawryś
    @Avasil
    @GKhotyan The code looks fine :)
    GKhotyan
    @GKhotyan
    Ok, great)