I’m a new scala user (my job now requires me to work on a small scala project). Coming from other programming languages, I really miss the file base approach when it comes to modules. Usually, I think of a file as a module whose goal is to provide some function(s) or class(es) to the rest of the system. While writing such module, I typically keep factoring out internal types and functions towards the beginning of the file (the end of the file being the actual stuff provided). Eventually, some of the internals can get promoted to another module (maybe some utils) if reusable enough.
I was quite happy to see that scala 3 allows for top-level definitions which are necessary for such workflow. However, when using what seems to be the typical package boundary, aka directories, anything top level I make private isn’t private to the file but to the directory (non-recursively). According to my tests having a package statement matching the file path is also allowed (and would make top-level definition private to the file), but I really don’t know if this approach is frowned upon for some reason (intelliji with the scala plugin certainly disapproves). Do you know of any projects using this style? What do you think of it?
Nope, I have a lot of packages that exist only on a single file, so that is fine.
We also usually do not force ourselves to make the packages and filesystem match, but just relate.
2 Likes
No, there are a lot of projects that do that (I’m working on one right now); it’s standard practice in Java projects so the habits carried over to many Scala projects as well.
1 Like
@spamegg1
I think you misunderstood me (sorry if my explanations were confusing). The repository you provided (cyfra) is a good example of the traditional java way where packages match directories. What I’m talking about would instead look something like this:
// myproject/src/main/scala/orgpath/myproject/somedir/myfile.scala
package orgpath.myproject.somedir.myfile
private def bar1(...) = ...
private def bar2(...) = ...
def foo(...) =
bar1(...)
bar2(...)
It really depends on the build tool. The java standard is to have package paths and source code paths align, and sbt and IntelliJ IDEA both lean into that.
But with scala-cli (= scala if you “install scala” rather than let your build tool handle it) you can just put the file right there and scala-cli run MyFile.scala, even if it’s package thisthat.whatchamacallit
. You don’t need src/main/scala
or anything.
Mill encourages a packagename/src/MyCode.scala
style, but generally without the entire package path, only the part that’s different. So if you have me.you.them.friends
as one package and me.you.them.enemies
as another, in mill you would expect to see friends/src/Friendly.scala
and enemies/src/Animous.scala
, even though internally the packages are the above.
1 Like
You’re right, I don’t get what that is, and I’ve never seen it. It actually might be frowned upon then… you should probably stick to the Java standard if you’re working on other people’s stuff.
Otherwise as Ichoran said, different build tools let you do different things. I’m a scala-cli person, in small projects I don’t worry about matching package/directory/file whatever; in larger projects I stick to the Java standard.
I would say the standard for these cases would be:
// myproject/src/main/scala/orgpath/myproject/somedir/myfile.scala
package orgpath .myproject.somedir
object MyFile:
private def bar1(...) = ...
private def bar2(...) = ...
def foo(...) =
bar1(...)
bar2(...)
P.S:
Packages and objects in Scala are very conceptually similar: They are both named things you put other things in, and that’s pretty much it
(Technically they are more different than that, but I’m not well versed in the details)
The main difference however, is that a package can span many files, whereas an object only one, which explains in part why we don’t see this:
object orgpath:
object myproject:
object MyFile:
...
But since you specifically want something single-file, object
is perfectly well-suited
It might even be superior, as it gives you the certainty everything really is in that file
2 Likes
Precisely because package
and object
feels similar, is that I would rather have a single file package rather than opening the file with a single object and ident everything just because.
1 Like
I understood the question to be whether a top-level definition can be made private to the file, and the answer is no, or rather a qualified no. (pun alert)
There was discussion somewhere about whether it should work that way, several years ago.
Since top-level defs are members of a file-specific “package object”, intuitively one might expect private[this]
to do the trick, but that syntax is no longer supported.
The qualification is that TIL the following does work in file priv1.scala
:
def trial() = 27 //+ p.f
package p:
//private def f = 42
//private[p] def f = 42
private[priv1$package] def f = 42 // visible only to top-level defs in this file
def g = f
class C:
def g = 42
//def g = f // disallowed
IIRC the naming of the package object is specified, although use of $
in source code names is discouraged.
The qualified private package member is not accessible from other files, as desired.
A related behavior is that definitions in the “unnamed package” can access definitions in a named package but not conversely. I see that the unnamed package cannot access package-private definitions, however, even if they are in the same file.
Similar glitches in “lexical scoping” are visible with opaque types that are “top-level”, such that what you see (as a hierarchy of scopes in the source text) is not exactly what you get.
2 Likes