I just realized there's a simple and nice solution to a longstanding annoyance: functions like File.readUtf8
take a Path
, which means in order to call them with a string you always have to do Path.fromStr
on that string first
it's important to have those operations available on Path
s, because you can get a Path
from things like Dir.list
and paths aren't necessarily valid strings (e.g. on UNIX, path can be any sequence of nonzero bytes, which is very much not necessarily valid UTF-8!)
but I just realized that we can do this to make the experience nicer:
File.roc
operations that take a Path
as their first argument into Path.roc
File
except they take Str
as their first argument as Path
, and now they're just convenience wrappers which do the Path.fromStr
for younow you have easy access to functions that take a Str
for convenience, and also if you have a Path
you can also easily call functions on those too using the Path
module!
curious what others think (cc @Luke Boswell)
I think this would be helpful. But it could be confusing, to have two functions doing the same, so an explanation somewhere in the docs would be good.
yeah, I think each of the functions could have a line at the end of its doc comment which notes its relationship to the one in the other module, and linking to that other one
I don't really follow the proposal here, specifically the following is confusing me.
Duplicate those back into File except they take Str as their first argument as Path, and now they're just convenience wrappers which do the Path.fromStr for you
Does this mean we would have the following? Why can't we just do this anyway and add another error tag for invalid path?
# uses a Str instead of a Path here?
File.writeBytes : Str, List U8 -> Task {} [FileWriteErr Path WriteErr]
oh sorry - so I'm saying we have these two:
File.writeBytes : Str, List U8 -> Task {} [FileWriteErr Path WriteErr]
Path.writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]
so they're exactly the same except for the first argument
including error type and everything
I'm still thinking about this... but one question this makes we think of; would it be possible to use an Ability here as the argument instead of Path? Like maybe anything that is toStr
able could be used??
You could make a toPath
ability that is on path, str, and list U8 theoretically.
But roc doesn't let you add an new ability to a builtin. So it doesn't really work
Brendan Hansknecht said:
But roc doesn't let you add an new ability to a builtin. So it doesn't really work
Is this a deliberate design decision? Is this so the dependencies are only one way and there are no cycles?
I think deliberate design to keep abilities simple/low in power, but not really sure
we actually talked about this explicit question at some point on Zulip a long time ago, I forget where
the way to do it would be something more like making a FromStr
builtin ability that opaque types could opt into
but the thing is, this is literally the only demand we've ever had for it, so I think it's better to go with a simpler solution for now
maybe if we accumulate a bunch of cases where it would be useful we could revisit that
but I think for now it's best to do something that works today and see how that feels
FromStr wouldn't work here. You want path to be generatable from a Str or a List U8
Cause a path doesn't need to be utf8
So I don't think there is a way to make a generic ability for this currently
Today you need either multiple separate functions or a tag
Last updated: Jul 06 2025 at 12:14 UTC