Printing to and reading from standard streams is a fairly pervasive operation, maybe less so in real codebases but certainly in numerous examples and gists in the wild. I've certainly seen and written a ridiculous amount of IO(println("foo")). If anything but for better ergonomics and out-of-the-box experience , we should think about introducing some utility functions to cats-effect to serve that need.
I like throwing out lists of bulletpoints for every issue I create so I don't have to answer these questions, so here's another one:
IO or F?putStrLn and getStrLn are pretty common operations to write, but not sure if there are more interesting operations to include. Another question is if these should be available in IO companion object or available as imports.Show integrationI鈥檇 really like a Console[F] as well as methods on IO companion. Gabriel recently commented in Gitter that he would prefer if the functionality of console4cats was builtin.
Interested in picking this up. Curious about a possible location for the code. A couple of ideas come to mind. Should this be a part of core or maybe a separate subproject console like concurrent.
+1 for part of core (and for IO, methods directly on IO companion)
@mpilquist Initially I thought the same. However, depending on core in a project brings in IO with it. In a way, users cannot really get access to a Console[F] without also getting the whole of IO in their project. Thoughts on this?
Yeah gotcha, I should have been more precise. I think we add Console to kernel and then add an implementation for IO in IO companion, along with direct methods like IO.println(..).
I'm not sure it should be in kernel. Instead, I would envision it as part of the increasingly-misnamed concurrent module. We should come up with a better name for that.
Ideally, there should be a Console[F[_]] trait which the IO companion can extend (and the Console companion can simply produce for any F[_]: Sync).
I wanted to port all of https://github.com/profunktor/console4cats so that includes things like putStrError which prints to the error stream. I'm worried that object IO extends Console[IO] will bring in more functionality than needed out of the box and polute the companion object. Also, future changes to Console will "leak" into the companion of IO.
Not to mention there is a mapK which would be super weird IMO.
I'd much rather go with a hand selected subset.
concurrent seems like a weird place for it (not saying kernel is great either). I don't care all that much as long as depending on core gives it to me and I don't need funky imports. Needs a very low barrier of entry.
Speaking of which, I'd much, much prefer if we use Scala naming conventions here instead of importing Haskell ones (looking at putStrLn vs println).
I agree concurrent is weird. My premise is basically that kernel is just the things necessary to get datatype implementations working, while core is "stuff that's doesn't make sense for not-IO", and the misnamed concurrent is basically what sits in between.
Speaking of which, I'd much, much prefer if we use Scala naming conventions here instead of importing Haskell ones (looking at putStrLn vs println).
Yeah I actually was going to bring that up. I don't see a reason not to use Console[F].println rather than Console[F].putStrLn. The latter just seems unnecessarily Haskell-ish. As a note, it should be safe now to have these methods take a value of type A: Show, since Show[String] is always in scope.
The original motivation for this was ergonomics for IO, so I'm in favor of minimal a barrier to entry as possible for IO users. So I think pulling a dependency in other than kernel or core is already too much.
Recall that the alternative is IO(println()) which is already very terse, so it needs to be just as easy or easier. I think avoiding ceremony in accessing this API is key. Also depending on how the API looks, I want to be able to use wildcard imports so maybe we want to avoid shadowing println?
// 1: companion object
IO.println("foo")
IO.readln()
// 2: Console[F]
import cats.effect.kernel.Console
val console = Console[IO]
import console._
printLine("foo")
readLine()
// 3: Console hardcoded to IO
import cats.effect.Console._
printLine("foo")
readLine()
I prefer the first or third, but now that I think about it we could also achieve the third API (with some concerns around type inference) with a polymorphic F if the companion object of Console required the constraints:
trait Console[F[_]] {
def printLine(msg: String): F[Unit]
}
object Console {
def printLine[F[_]: Console](msg: String): F[Unit] = ???
}
Since we are discussing familiarity for Scala users, Scala has println(...) and readLine() from StdIn.
@RaasAhsan the code being in concurrent still keeps the same ergonomics for IO users as core depends on concurrent.
Most helpful comment
I agree concurrent is weird. My premise is basically that kernel is just the things necessary to get datatype implementations working, while core is "stuff that's doesn't make sense for not-
IO", and the misnamed concurrent is basically what sits in between.Yeah I actually was going to bring that up. I don't see a reason not to use
Console[F].printlnrather thanConsole[F].putStrLn. The latter just seems unnecessarily Haskell-ish. As a note, it should be safe now to have these methods take a value of typeA: Show, sinceShow[String]is always in scope.