raceN
: https://gist.github.com/ChristopherDavenport/f75012bc89a7b7871f56fd14bf906d5f
IO
right? and now all calling functions must return some form of IO[F]
or a monad transformer or something (when say previously they just returned an Option
or something)
Like, if I have a bunch of nested functions, and one deeply nested function wants to get command line input or something, that function needs to return an
IO
right? and now all calling functions must return some form ofIO[F]
or a monad transformer or something (when say previously they just returned anOption
or something)
LiftIO
basically solves that problem. If you have a LiftIO[F]
, then even if you have something that concretely returns an IO[A]
, you can get an F[A]
out of it.
Also on another note, what is
F
actually termed as, as it's used in the cats documentation? a container? a single holed type? or something else?
:-) Usually we use A
for something like IO[A]
, since we often use F
in the context of F[A]
or similar. And to answer your question, it's usually termed "the value", even though that may not be precisely accurate
and now all calling functions must return some form of IO[F]
Do you have a specific reason to be against this?. It doesn't have to bubble up but it is highly recommended that you run it at the end of the world. Obvious power of IO is the easy composibility and how the underlying IOs can be run without much hassle (I recommend watching @SystemFw 's amazing talk on Fibers). If you run the IO within the nested function, it is not gonna be much different than awaiting and you throw away the concurrency and all the advantages cats give you.
Like, if I have a bunch of nested functions, and one deeply nested function wants to get command line input or something, that function needs to return an IO right? and now all calling functions must return some form of IO[F] or a monad transformer or something (when say previously they just returned an Option or something)
Also I realized upon re-reading the rest of your messages that you were probably asking a different question than what I answered. :-)
Yes, IO
always "bubbles up" in your program. The only way to eliminate an IO[A]
(and get an A
) is to use unsafeRunSync()
or similar, but as the name implies, this is very... unsafe. :-) The reason this is unsafe is two-fold. First, if the function which returns an IO[A]
uses any sort of asynchronous machinery (which you can't know without inspecting it!), then unsafeRunSync()
will block a thread unnecessarily. This is anathema to any sort of scale or throughput and will lead to thread starvation, or in the worst case, deadlocks.
The second reason is a bit trickier, but it boils down to what we often refer to as "referential transparency". If I have an expression that looks like foo(somethingComplex, somethingComplex)
, I might want to take somethingComplex
and put it into a val a = somethingComplex
, so that I can do foo(a, a)
. When I do this, I'm exploiting referential transparency. It literally just means "you can safely factor out duplication without changing your program".
Except you can't do this if somethingComplex
involves an unsafe
function. You can do it if it involves IO
, but not if it involves unsafe
. So you lose some ability to refactor and reason about your program whenever you use unsafe
things.
This is why IO
does "bubble out", as you observed, and that's a good thing. :-)
private def checkValidPair(argsPair: List[String]): Either[String, Unit] = argsPair.head match {
case "--f" => checkFileExists[IO](argsPair.tail.head).ifM("This is where things went wrong :)")
}
private def checkFileExists[F[_]: Sync](file: String): F[Boolean] =
Sync[F].delay(Files.exists(Paths.get(file)))
private def parseArgs(args: List[String]): Either[String, Unit] =
for {
_ <- checkArgsEmpty(args)
pair <- args.sliding(2).toList
_ <- checkIfPair(pair)
_ <- checkValidFlag(pair.head)
_ <- checkValidPair(pair)
}
Either[String, Unit]
. And then the side effecting function checkValidPair
could be called separately. Or I could change every function to return a EitherT[IO, String, Unit]
or something like that - but that's what I was trying to avoid hence the bubble up question
parseArgs
is the entry point btw
Validated
but I totally forgot to look into that
Bracket
or Resource
?Resource
:)
Concurrent.background
IIRC
val longProcess = (IO.sleep(5.seconds) *> IO(println("Ping!"))).foreverM
val srv: Resource[IO, ServerBinding[IO]] = for {
_ <- longProcess.background
server <- server.run
} yield server
val application = srv.use(binding => IO(println("Bound to " + binding)) *> IO.never)
What's server.run ?
Resource
is being used to scope the lifecycle of some dummy application that starts a longProcess
and some kind of network server, both of which are captured via Resource