Open TonioGela opened 1 month ago
Thinking out loud here. Theoretically to write/append to a file we should call this API:
def writeAll(path: Path, flags: Flags): Pipe[F, Byte, Nothing]
with either Flags.Write
or Flags.Append
and then piping a Stream[F, Byte]
in it.
How about splitting it into a few methods like these?
(path: Path).write(bytes: Array[Byte]): IO[Unit]
(path: Path).writeUtf8(s: String): IO[Unit]
(path: Path).append(bytes: Array[Byte]): IO[Unit]
(path: Path).appendUtf8(s: String): IO[Unit]
I would also love to hear everybody's opinion about having some kind of file-contents'-handle like structure, a generalization of this 👇
case class Score(name: String, score: Int):
def to: String = s"$name:$score"
object Score:
def from(s:String)(line: Long): IO[Score] = s.split(':') match
case Array(name, score) => IO(Score(name, score.toInt))
case _ => IO.raiseError(new Exception(s"Malformed score $s at line $line"))
def readScores: IO[List[Score]] = Files[IO].readUtf8Lines(Path("/tmp/score"))
.filterNot(_.isBlank).zipWithIndex.evalMap((s,i) => Score.from(s)(i)).compile.toList
def saveScores(scores: List[Score]): IO[Unit] = Stream.emits(scores).map(_.to)
.through(Files[IO].writeUtf8Lines(Path("/tmp/.config/score"))).compile.drain
def scoreResource: Resource[IO, Ref[IO, List[Score]]] = Resource(
readScores.flatMap(Ref.of).fproduct(_.get.flatMap(saveScores))
)
that you can use in a Resource
like fashion:
scoreResource.use(_.update(Score("tonio", 100) :: _))
Aside: I would avoid use of Array[Byte]
in the APIs and use Chunk[Byte]
instead.
use
Chunk[Byte]
instead.
My vote would be for ByteVector
. It directly offers more useful APIs for working with Byte
s e.g. decoding into a String
. See also my reasoning in https://github.com/http4s/http4s/pull/6528.
Let's keep in mind that to make things "really fluent", we can also think of a way to add a syntax over Path, so that we could be able to call the same APIs as
(p: Path).readAll: IO[Array[Byte]]
This syntax is a bit awkward imho, ex:
p"$temp_dir/log.txt".readAll
// or
"user/hom/dir/other_dir/file.txt".readAll
So I prefer something like:
Files.readAll(path: Path): IO[ByteVector]
// or
Shellfish.readAll(path: Path): IO[ByteVector]
// or
Shellfish.files.readAll(path: Path): IO[ByteVector] // os lib style
I was more thinking of something like
val path = Path("/path/to/directory")
for
lines <- path.readUtf8Lines
uppercased = lines.map(_.toUpperCase)
newPath = path + "_uppercased"
_ <- newPath.writeUtf8Lines(uppercased)
yield ()
The first step after #55 should be to add to the library the functionalities available in Files, keeping in mind that we decided to hardcode to
IO
, to do not use the tagless final style, and the end goal is to provide a layer of fluent APIs.The rough way I've pictured this in my head is to create an object call that extends
Files[IO]
and that takes APIs like:and re-exports them as their "stricter, IO hardcoded" version:
Let's keep in mind that to make things "really fluent", we can also think of a way to add a
syntax
overPath
, so that we could be able to call the same APIs asthat is something that fits well (IMHO) in a for comprehension where files are read/created/etc.
Let's also keep in mind that possibly not all the
Files
methods will need to be exposed (at least at the beginning).If we need some inspiration to add some helpful method, we might have a look at
java.nio.Files
or os-lib.