The problem is that API functions like tmp-dir! inevitably suggest that we're working with the filesystem, but this too is an implementation detail. Boot's current implementation of tmp-dir! happens to create a hidden temporary directory, but an alternative could use some other mechanism, such as an in-memory database like Datascript. Furthermore, the fact that one uses Java filesystem APIs to work with the temp dir is also an implementation detail. An alternative could offer its own more abstract API, with functions like add-item or similar. What's important is that in order to alter the Fileset the developer must obtain a workspace, then add, change, and delete items in the workspace, and finally merge the workspace with the incoming Fileset to create a new Fileset to be passed on to subsequent tasks in the pipeline. The implementation mechanism is not relevant to the semantics, so ideally, the API would be expressed solely in terms of implementation-independent abstractions.
When I say that the concept of a workspace is Boot's missing abstraction I do not mean that Boot lacks some critical bit of functionality that workspaces would provide. I mean just that promoting the concept of a workspace to first-class status would make it easier for developers and users to understand and use Boot.
For example, my recommendation is that people new to Boot think of boot.core/tmp-dir! as boot.core/workspace, and should avoid thinking of workspaces as filesystem directories and their contents as files. A workspace is just a space, never mind how the implementation handles the mapping of that space to a storage mechanism. The concept is very similar to the notion of a namespace in Clojure. The Java implementation of Clojure maps namespace symbols to file names, but that too is an implementation detail; when you work with namespaces and their contents in Clojure you never think about filesystem locations, you just think about names in namespaces. Similarly for Boot workspaces; when you add something to a workspace and then merge it with a Fileset you don't care how Boot goes about making this happen behind the scenes, you just want the stuff you put in the workspace to end up in the Fileset.
Unfortunately it's a little harder to ignore the filesystem-based implementation when you go to add something to a workspace. A common pattern looks something like this:
(let [tmp-dir (boot/tmp-dir!)
out-file (io/file tmp-dir "hello.txt")]
(spit out-file "hello world")
(-> fileset (boot/add-asset tmp-dir) boot/commit!))
Obviously it's hard to think of io/file as anything other than a filesystem operation. But it's easy to imagine a few macros that would provide a more abstract interface. So the above might look something like:
(let [ws (-> (boot/workspace) (boot/add-item "hello.txt" "hello world"))]
(-> fileset (boot/add-asset ws) boot/commit!))
(-> fileset (boot/add-asset ws) boot/commit!))
(let [ws (boot/workspace foo)
wss (add-item ws hello.txt "hello world")]
(-> fileset (boot/add-asset wss) boot/commit!))
wss (add-item ws hello.txt "hello world")]
(-> fileset (boot/add-asset wss) boot/commit!))
Or you might be even more Clojure-like, and support workspace operations like find-ws, so instead of the preceding you could write something like
(boot/workspace foo)
(boot/workspace bar)]
...
(let [ws (boot/find-ws foo)]
(add-item ws hello.txt "hello world")
(-> fileset ws boot/commit!))
You could even think of Fileset as a species of Workspace, a kind of lambda workspace.
(boot/workspace foo)
(boot/workspace bar)]
...
(let [ws (boot/find-ws foo)]
(add-item ws hello.txt "hello world")
(-> fileset ws boot/commit!))
You could even think of Fileset as a species of Workspace, a kind of lambda workspace.
No comments:
Post a Comment