Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing Files operations #63

Open
TonioGela opened this issue Jun 1, 2024 · 7 comments
Open

Implementing Files operations #63

TonioGela opened this issue Jun 1, 2024 · 7 comments

Comments

@TonioGela
Copy link
Contributor

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:

def readAll(path: Path): Stream[F, Byte]

and re-exports them as their "stricter, IO hardcoded" version:

def readAll(path: Path): IO[Array[Byte]]

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]]

that 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.

@TonioGela
Copy link
Contributor Author

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]

@TonioGela
Copy link
Contributor Author

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) :: _))

@mpilquist
Copy link

Aside: I would avoid use of Array[Byte] in the APIs and use Chunk[Byte] instead.

@armanbilge
Copy link
Contributor

use Chunk[Byte] instead.

My vote would be for ByteVector. It directly offers more useful APIs for working with Bytes e.g. decoding into a String. See also my reasoning in http4s/http4s#6528.

@lenguyenthanh
Copy link
Member

lenguyenthanh commented Jun 10, 2024

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

@TonioGela
Copy link
Contributor Author

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 ()

@Hombre-x
Copy link
Contributor

Speaking of #80, can we also discuss the FilesOs name? Maybe just leave it as File?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants