Support multiple files and package imports
Currently, the Fo CLI only works on one file at a time. This is fine for developing a proof of concept and polishing core language features, but in order for Fo to be practical for writing real-world applications, we need to support packages with multiple files and importing other packages (whether they are written in Go or Fo).
I've been thinking a lot about how to do this while maximizing compatibility with the existing Go ecosystem and build tools. Here's how I think it should work:
- A Fo package must live in
$GOPATHand vendor all dependencies in a top-level vendor/ directory. - A new
fo buildcommand will generate the corresponding .go files right along side the original .fo source files recursively for each sub-package and all dependencies in the vendor/ directory. So if you have a source file located at package/subpackage/file.fo,fo buildwill compile it to package/subpackage/file.go. The Fo compiler checks all files and packages together in order to guarantee that concrete types are generated as needed based on usage. - After compiling with
fo build, you can use all the commands in the Go CLI (e.g.go install,go test,go build) as you would normally. The Go compiler will only care about the .go files which, by design, are always in the location it expects. - Importing a third-party Go package as a dependency works as normal and can be installed with your package manager of choice.
- Importing a third-party Fo package as a dependency works as normal and can be installed with your package manager of choice. However, there is a caveat that the package developer may need to add a dummy .go file in order to work with some package managers.
As an obvious future improvement, a fo watch command can be implemented which automatically generates .go files as needed whenever a .fo file changes.
One downside to this approach is that generated concrete types are not shared between packages with different vendor/ directories. For example if package a and package b both depend on package c and use type c.T[string], there will be two identical concrete types generated in a/vendor/c/... and b/vendor/c/.... However, disk space is cheap and getting cheaper, and the simplicity that comes from this approach makes it preferable. Additionally, using the approach described here does not preclude the possibility of implementing a more disk space-efficient method in the future.
792cc6b8d84c8c3a1f6020faac4ce26e84decc76 gets us one step closer to this. It is now possible to import Go packages outside of the standard library. However, importing Fo packages is not yet supported. When importing Fo packages, there are some potential issues around circular dependencies that I expect will be tricky to figure out.
I've been working a lot on this recently. The problem with my original plan is that it creates cyclical imports. So to work around this, I had to redesign the code generation strategy. If you're curious, you can track progress on the interface-boxing-unboxing branch