Two years have passed and I still love writing Standard ML code! I've learned one thing and another about structuring the code in order to share it with ease and produce better development experiences.
After trying out quite a few build systems, I decided to settle on MLBasis and Makefiles. While MLBasis is not a build system itself, you can use it to define what parts of the code should be exported to other modules or programs. I had a much better experience with it compared to cm or PolyML's own build system.
The downside of MLBasis is that not all compilers supports it. As far as I know, you are bound to use MLton, mlkit and LunarML. Which is fine for me, but might not be for everyone.
Let's get into the structure that I found the best to use for general libraries and programs:
lib.mlb
Makefile
src/
|-- *.sml
`-- sources.mlb
tests/
|-- *.sml
`-- tests.mlb
It all starts with a lib.mlb file at the root of the repository1. This file is responsible for defining what modules will be exported by the libraries or what is the program's entrypoint. Here's an example from my testing framework:
(* lib.mlb *)
local
src/sources.mlb
in
signature CONFIGURATION
signature TEST
signature EXPECT
signature RAILROAD
structure Railroad
end
It's now obvious that we should talk about src/sources.mlb. This file is responsible for listing all the source files of a project, but why? The reason is simple enough, by simply listing the files, everything will be "exported" by default and makes our lives much easier for testing internals as everything is made available for us!
(* src/sources.mlb *)
$(SML_LIB)/basis/basis.mlb
configuration.sml
expectation.sml
...
Now that tests are mentioned, let's talk about tests.mlb. This is the unit that holds the entrypoint for your tests. Here's what it looks like:
(* tests/tests/mlb *)
$(SML_LIB)/basis/basis.mlb
../src/sources.mlb
src/helpers.sml
src/expectation.sml
...
You can see that ../src/sources.mlb is declared, this is what makes it possible to test all internal knobs of our code.
With this, you will be able to have two main commands during development phase that will do most of the work:
# compiles and runs your code
$ smlc -o build/main lib.mlb && ./build/main
# compiles and run your tests
$ smlc -o build/tests tests/tests.mlb && ./build/tests
-
It doesn't really matter if it's a program or a library. I just found
lib.mlbto be an acceptable name for a program whilemain.mlbfelt weird for libraries. Now that I think about,index.mlbmight be a better name???↩